diff --git a/js/.changeset/add-link-interfaces.md b/js/.changeset/add-link-interfaces.md
new file mode 100644
index 0000000..e65257a
--- /dev/null
+++ b/js/.changeset/add-link-interfaces.md
@@ -0,0 +1,12 @@
+---
+'links-queue-js': minor
+---
+
+Add Link and LinkStore interfaces for Phase 1 API contract
+
+- Add `Link` interface with `id`, `source`, `target`, and optional `values` properties
+- Add `LinkRef` and `LinkId` types for flexible link referencing
+- Add `LinkStore` interface with CRUD operations (create, get, find, update, delete, etc.)
+- Add `LinkPattern` interface with `Any` wildcard for pattern matching
+- Add utility functions: `isLink`, `isLinkId`, `isLinkRef`, `getLinkId`, `createLink`, `matchesPattern`
+- Compatible with links-notation and doublets-rs patterns
diff --git a/js/examples/basic-usage.js b/js/examples/basic-usage.js
index 755ce81..9dd234f 100644
--- a/js/examples/basic-usage.js
+++ b/js/examples/basic-usage.js
@@ -1,6 +1,7 @@
/**
- * Basic usage example
- * Demonstrates how to use the package
+ * Basic usage examples for links-queue
+ *
+ * This file demonstrates the core Link and LinkStore interfaces.
*
* Run with any runtime:
* - Node.js: node examples/basic-usage.js
@@ -8,20 +9,154 @@
* - Deno: deno run examples/basic-usage.js
*/
-import { add, multiply, delay } from '../src/index.js';
+import {
+ Any,
+ isLink,
+ isLinkId,
+ isLinkRef,
+ getLinkId,
+ createLink,
+ matchesPattern,
+} from '../src/index.js';
+
+// =============================================================================
+// Creating Links
+// =============================================================================
+
+console.log('=== Creating Links ===\n');
+
+// Simple link with numeric IDs
+const simpleLink = createLink(1, 2, 3);
+console.log('Simple link:', simpleLink);
+// Output: { id: 1, source: 2, target: 3 }
+
+// Link with bigint IDs (for large datasets)
+const bigintLink = createLink(1n, 9007199254740993n, 9007199254740994n);
+console.log('Bigint link:', bigintLink);
+
+// Link with string IDs (for UUID-based systems)
+const uuidLink = createLink('link-001', 'source-uuid-123', 'target-uuid-456');
+console.log('UUID link:', uuidLink);
+
+// =============================================================================
+// Nested Links (Recursive Structures)
+// =============================================================================
+
+console.log('\n=== Nested Links ===\n');
+
+// Create nested link structures for representing complex relationships
+const personType = createLink(10, 0, 0); // A "type" link
+const nameProperty = createLink(11, 0, 0); // A "name" property link
+const person = createLink(1, personType, nameProperty);
+console.log('Person link with type and name:', person);
+
+// Deep nesting: (1: (2: (3: 30 31)))
+const level3 = createLink(3, 30, 31);
+const level2 = createLink(2, level3, 21);
+const level1 = createLink(1, level2, 11);
+console.log('Deeply nested:', level1);
+console.log(' Level 1 source ID:', level1.source.id); // 2
+console.log(' Level 2 source ID:', level1.source.source.id); // 3
+
+// =============================================================================
+// Universal Links (Variable Number of References)
+// =============================================================================
+
+console.log('\n=== Universal Links ===\n');
+
+// Universal link with additional values beyond source/target
+// Useful for n-ary relationships
+const tripleRelation = createLink(
+ 100, // id
+ 1, // source (subject)
+ 2, // target (predicate)
+ [3, 4, 5] // values (objects)
+);
+console.log('Triple+ relation:', tripleRelation);
+
+// =============================================================================
+// Type Checking
+// =============================================================================
+
+console.log('\n=== Type Checking ===\n');
+
+console.log(
+ 'isLink({ id: 1, source: 2, target: 3 }):',
+ isLink({ id: 1, source: 2, target: 3 })
+); // true
+console.log('isLink(42):', isLink(42)); // false
+console.log('isLink(null):', isLink(null)); // false
+
+console.log('isLinkId(42):', isLinkId(42)); // true
+console.log('isLinkId(1n):', isLinkId(1n)); // true
+console.log('isLinkId("uuid"):', isLinkId('uuid')); // true
+console.log('isLinkId({}):', isLinkId({})); // false
+
+console.log('isLinkRef(42):', isLinkRef(42)); // true
+console.log('isLinkRef(simpleLink):', isLinkRef(simpleLink)); // true
+
+// =============================================================================
+// Extracting IDs
+// =============================================================================
+
+console.log('\n=== Extracting IDs ===\n');
+
+console.log('getLinkId(42):', getLinkId(42)); // 42
+console.log('getLinkId(simpleLink):', getLinkId(simpleLink)); // 1
+
+const nestedSource = createLink(5, 50, 51);
+const linkWithNestedSource = createLink(1, nestedSource, 10);
+console.log(
+ 'getLinkId(linkWithNestedSource.source):',
+ getLinkId(linkWithNestedSource.source)
+); // 5
+
+// =============================================================================
+// Pattern Matching
+// =============================================================================
+
+console.log('\n=== Pattern Matching ===\n');
+
+const links = [
+ createLink(1, 10, 20),
+ createLink(2, 10, 30),
+ createLink(3, 20, 30),
+ createLink(4, 30, 40),
+];
+
+// Find links with source = 10
+const sourcePattern = { source: 10 };
+const withSource10 = links.filter((link) =>
+ matchesPattern(link, sourcePattern)
+);
+console.log(
+ 'Links with source=10:',
+ withSource10.map((l) => l.id)
+); // [1, 2]
+
+// Find links with target = 30
+const targetPattern = { target: 30 };
+const withTarget30 = links.filter((link) =>
+ matchesPattern(link, targetPattern)
+);
+console.log(
+ 'Links with target=30:',
+ withTarget30.map((l) => l.id)
+); // [2, 3]
-// Example: Using add function
-console.log('Addition examples:');
-console.log(` 2 + 3 = ${add(2, 3)}`);
-console.log(` -1 + 5 = ${add(-1, 5)}`);
+// Find links with any source and target = 30
+const anySourcePattern = { source: Any, target: 30 };
+const anySourceTarget30 = links.filter((link) =>
+ matchesPattern(link, anySourcePattern)
+);
+console.log(
+ 'Links with Any source, target=30:',
+ anySourceTarget30.map((l) => l.id)
+); // [2, 3]
-// Example: Using multiply function
-console.log('\nMultiplication examples:');
-console.log(` 4 * 5 = ${multiply(4, 5)}`);
-console.log(` -2 * 3 = ${multiply(-2, 3)}`);
+// Match all links
+const allPattern = { source: Any, target: Any };
+const allMatched = links.filter((link) => matchesPattern(link, allPattern));
+console.log('All links (Any, Any):', allMatched.length); // 4
-// Example: Using async delay function
-console.log('\nAsync example:');
-console.log(' Waiting 100ms...');
-await delay(100);
-console.log(' Done!');
+console.log('\n=== Done ===\n');
diff --git a/js/src/index.d.ts b/js/src/index.d.ts
index 658cd82..610313c 100644
--- a/js/src/index.d.ts
+++ b/js/src/index.d.ts
@@ -1,27 +1,161 @@
/**
- * Example module type definitions
- * Replace this with your actual type definitions
+ * links-queue - Core type definitions
+ *
+ * This module exports the Link and LinkStore interfaces that form
+ * the API contract for all implementations.
*/
+// =============================================================================
+// ID and Reference Types
+// =============================================================================
+
+/**
+ * A link identifier. Can be a number, bigint, or string.
+ */
+export type LinkId = number | bigint | string;
+
+/**
+ * A reference to a link, which can be an ID or a nested Link object.
+ */
+export type LinkRef = LinkId | Link;
+
+// =============================================================================
+// Link Interface
+// =============================================================================
+
+/**
+ * Represents a link (associative data structure) connecting source to target.
+ */
+export interface Link {
+ readonly id: LinkId;
+ readonly source: LinkRef;
+ readonly target: LinkRef;
+ readonly values?: readonly LinkRef[];
+}
+
+// =============================================================================
+// Pattern Matching
+// =============================================================================
+
+/**
+ * Special value indicating "any" match in pattern queries.
+ */
+export declare const Any: unique symbol;
+export type AnyType = typeof Any;
+
+/**
+ * Pattern for matching links in queries.
+ */
+export interface LinkPattern {
+ readonly id?: LinkId | AnyType;
+ readonly source?: LinkRef | AnyType;
+ readonly target?: LinkRef | AnyType;
+}
+
+// =============================================================================
+// LinkStore Interface
+// =============================================================================
+
+/**
+ * Interface for link storage operations.
+ */
+export interface LinkStore {
+ create(source: LinkRef, target: LinkRef): Promise;
+ createWithValues(
+ source: LinkRef,
+ target: LinkRef,
+ values: readonly LinkRef[]
+ ): Promise;
+ get(id: LinkId): Promise;
+ exists(id: LinkId): Promise;
+ find(pattern: LinkPattern): Promise;
+ count(pattern?: LinkPattern): Promise;
+ update(id: LinkId, source: LinkRef, target: LinkRef): Promise;
+ delete(id: LinkId): Promise;
+ deleteMatching(pattern: LinkPattern): Promise;
+ iterate(pattern?: LinkPattern): AsyncIterable;
+}
+
+// =============================================================================
+// Utility Types
+// =============================================================================
+
+export type LinkIdOf = L['id'];
+
+export interface MutableLink {
+ id: LinkId;
+ source: LinkRef;
+ target: LinkRef;
+ values?: LinkRef[];
+}
+
+export interface CreateLinkOptions {
+ source: LinkRef;
+ target: LinkRef;
+ values?: LinkRef[];
+}
+
+export type LinkResult =
+ | { success: true; value: T }
+ | { success: false; error: string };
+
+// =============================================================================
+// Utility Functions
+// =============================================================================
+
+/**
+ * Checks if a value is a Link object.
+ */
+export declare const isLink: (value: unknown) => value is Link;
+
+/**
+ * Checks if a value is a valid LinkId.
+ */
+export declare const isLinkId: (value: unknown) => value is LinkId;
+
+/**
+ * Checks if a value is a valid LinkRef.
+ */
+export declare const isLinkRef: (value: unknown) => value is LinkRef;
+
+/**
+ * Extracts the LinkId from a LinkRef.
+ */
+export declare const getLinkId: (ref: LinkRef) => LinkId;
+
+/**
+ * Creates a Link object from source and target references.
+ */
+export declare const createLink: (
+ id: LinkId,
+ source: LinkRef,
+ target: LinkRef,
+ values?: LinkRef[]
+) => Link;
+
+/**
+ * Checks if a pattern matches a link.
+ */
+export declare const matchesPattern: (
+ link: Link,
+ pattern: LinkPattern
+) => boolean;
+
+// =============================================================================
+// Deprecated exports (keep for backward compatibility)
+// =============================================================================
+
/**
- * Adds two numbers
- * @param a - First number
- * @param b - Second number
- * @returns Sum of a and b
+ * @deprecated Use the new Link interface
*/
export declare const add: (a: number, b: number) => number;
/**
- * Multiplies two numbers
- * @param a - First number
- * @param b - Second number
- * @returns Product of a and b
+ * @deprecated Use the new Link interface
*/
export declare const multiply: (a: number, b: number) => number;
/**
- * Delays execution for specified milliseconds
- * @param ms - Milliseconds to wait
- * @returns Promise that resolves after the delay
+ * @deprecated Will be removed in future versions
*/
export declare const delay: (ms: number) => Promise;
diff --git a/js/src/index.js b/js/src/index.js
index 22705fd..d1dd98e 100644
--- a/js/src/index.js
+++ b/js/src/index.js
@@ -1,10 +1,151 @@
/**
- * Example module entry point
- * Replace this with your actual implementation
+ * links-queue - Core module entry point
+ *
+ * This module exports the Link and LinkStore interface definitions
+ * along with utility functions and constants.
*/
+// =============================================================================
+// Pattern Matching Constants
+// =============================================================================
+
+/**
+ * Special value indicating "any" match in pattern queries.
+ * Use this in LinkPattern to match any value in that position.
+ *
+ * @example
+ * // Find all links with source = 5, any target
+ * const links = await store.find({ source: 5, target: Any });
+ */
+export const Any = Symbol('Any');
+
+// =============================================================================
+// Utility Functions
+// =============================================================================
+
+/**
+ * Checks if a value is a Link object (has id, source, target properties).
+ *
+ * @param {unknown} value - The value to check
+ * @returns {boolean} True if value is a Link object
+ *
+ * @example
+ * isLink({ id: 1, source: 2, target: 3 }) // true
+ * isLink(42) // false
+ * isLink("hello") // false
+ */
+export const isLink = (value) =>
+ value !== null &&
+ typeof value === 'object' &&
+ 'id' in value &&
+ 'source' in value &&
+ 'target' in value;
+
+/**
+ * Checks if a value is a valid LinkId (number, bigint, or string).
+ *
+ * @param {unknown} value - The value to check
+ * @returns {boolean} True if value is a valid LinkId
+ *
+ * @example
+ * isLinkId(42) // true
+ * isLinkId(9007199254740993n) // true
+ * isLinkId("uuid-here") // true
+ * isLinkId({}) // false
+ */
+export const isLinkId = (value) =>
+ typeof value === 'number' ||
+ typeof value === 'bigint' ||
+ typeof value === 'string';
+
+/**
+ * Checks if a value is a valid LinkRef (LinkId or Link).
+ *
+ * @param {unknown} value - The value to check
+ * @returns {boolean} True if value is a valid LinkRef
+ *
+ * @example
+ * isLinkRef(42) // true
+ * isLinkRef({ id: 1, source: 2, target: 3 }) // true
+ */
+export const isLinkRef = (value) => isLinkId(value) || isLink(value);
+
+/**
+ * Extracts the LinkId from a LinkRef.
+ * If the ref is a Link, returns its id. Otherwise returns the ref itself.
+ *
+ * @param {import('./index.d.ts').LinkRef} ref - The reference to extract ID from
+ * @returns {import('./index.d.ts').LinkId} The extracted LinkId
+ *
+ * @example
+ * getLinkId(42) // 42
+ * getLinkId({ id: 1, source: 2, target: 3 }) // 1
+ */
+export const getLinkId = (ref) => (isLink(ref) ? ref.id : ref);
+
+/**
+ * Creates a Link object from source and target references.
+ *
+ * @param {import('./index.d.ts').LinkId} id - The link ID
+ * @param {import('./index.d.ts').LinkRef} source - The source reference
+ * @param {import('./index.d.ts').LinkRef} target - The target reference
+ * @param {import('./index.d.ts').LinkRef[]} [values] - Optional additional values
+ * @returns {import('./index.d.ts').Link} The created Link object
+ *
+ * @example
+ * const link = createLink(1, 2, 3);
+ * console.log(link); // { id: 1, source: 2, target: 3 }
+ *
+ * const universalLink = createLink(1, 2, 3, [4, 5]);
+ * console.log(universalLink); // { id: 1, source: 2, target: 3, values: [4, 5] }
+ */
+export const createLink = (id, source, target, values = undefined) => {
+ const link = { id, source, target };
+ if (values !== undefined && values.length > 0) {
+ link.values = values;
+ }
+ return Object.freeze(link);
+};
+
+/**
+ * Checks if a pattern matches a link.
+ *
+ * @param {import('./index.d.ts').Link} link - The link to check
+ * @param {import('./index.d.ts').LinkPattern} pattern - The pattern to match against
+ * @returns {boolean} True if the link matches the pattern
+ *
+ * @example
+ * const link = { id: 1, source: 2, target: 3 };
+ * matchesPattern(link, { source: 2 }) // true
+ * matchesPattern(link, { source: 5 }) // false
+ * matchesPattern(link, { source: Any }) // true
+ */
+export const matchesPattern = (link, pattern) => {
+ if (pattern.id !== undefined && pattern.id !== Any) {
+ if (getLinkId(link.id) !== getLinkId(pattern.id)) {
+ return false;
+ }
+ }
+ if (pattern.source !== undefined && pattern.source !== Any) {
+ if (getLinkId(link.source) !== getLinkId(pattern.source)) {
+ return false;
+ }
+ }
+ if (pattern.target !== undefined && pattern.target !== Any) {
+ if (getLinkId(link.target) !== getLinkId(pattern.target)) {
+ return false;
+ }
+ }
+ return true;
+};
+
+// =============================================================================
+// Backward Compatible Exports (deprecated)
+// =============================================================================
+
/**
* Example function that adds two numbers
+ * @deprecated Use the new Link interfaces instead
* @param {number} a - First number
* @param {number} b - Second number
* @returns {number} Sum of a and b
@@ -13,6 +154,7 @@ export const add = (a, b) => a + b;
/**
* Example function that multiplies two numbers
+ * @deprecated Use the new Link interfaces instead
* @param {number} a - First number
* @param {number} b - Second number
* @returns {number} Product of a and b
@@ -21,6 +163,7 @@ export const multiply = (a, b) => a * b;
/**
* Example async function
+ * @deprecated Will be removed in future versions
* @param {number} ms - Milliseconds to wait
* @returns {Promise}
*/
diff --git a/js/src/types.ts b/js/src/types.ts
new file mode 100644
index 0000000..118f4df
--- /dev/null
+++ b/js/src/types.ts
@@ -0,0 +1,416 @@
+/**
+ * Core Link and LinkStore type definitions for links-queue.
+ *
+ * This module provides the foundational interfaces for the Link data model
+ * and LinkStore operations. These interfaces establish the API contract that
+ * implementations must follow.
+ *
+ * @module types
+ *
+ * Design goals:
+ * - Compatible with links-notation (supports nested/recursive structures)
+ * - Compatible with doublets-rs patterns (source/target model)
+ * - Extensible for universal links with any number of references
+ *
+ * @see https://github.com/link-foundation/links-notation
+ * @see https://github.com/linksplatform/doublets-rs
+ */
+
+// =============================================================================
+// ID Types
+// =============================================================================
+
+/**
+ * A link identifier. Can be a number, bigint, or string.
+ *
+ * @example
+ * const numericId: LinkId = 42;
+ * const bigId: LinkId = 9007199254740993n;
+ * const stringId: LinkId = "custom-uuid-here";
+ */
+export type LinkId = number | bigint | string;
+
+// =============================================================================
+// Link Reference Types
+// =============================================================================
+
+/**
+ * A reference to a link, which can be:
+ * - A link ID (direct reference by identifier)
+ * - A nested Link object (inline definition)
+ *
+ * This allows for flexible link compositions and nested structures,
+ * supporting arbitrary levels of recursion as required by links-notation.
+ *
+ * @example
+ * // Direct reference by ID
+ * const ref1: LinkRef = 42;
+ *
+ * // Nested link reference
+ * const ref2: LinkRef = { id: 1, source: 2, target: 3 };
+ *
+ * // Deeply nested
+ * const ref3: LinkRef = {
+ * id: 1,
+ * source: { id: 2, source: 3, target: 4 },
+ * target: 5
+ * };
+ */
+export type LinkRef = LinkId | Link;
+
+// =============================================================================
+// Link Interface
+// =============================================================================
+
+/**
+ * Represents a link (associative data structure) connecting source to target.
+ *
+ * A Link is the fundamental unit of data in links-queue. It represents
+ * a directed relationship from a source to a target, identified by a unique ID.
+ *
+ * For compatibility with links-notation's universal link model, additional
+ * values can be provided beyond the basic source/target pair.
+ *
+ * @property id - Unique identifier for this link
+ * @property source - Reference to the source (can be another Link or LinkId)
+ * @property target - Reference to the target (can be another Link or LinkId)
+ * @property values - Optional additional values for universal links (extensibility)
+ *
+ * @example
+ * // Simple doublet-style link
+ * const link: Link = {
+ * id: 1,
+ * source: 2,
+ * target: 3
+ * };
+ *
+ * @example
+ * // Nested link structure
+ * const nestedLink: Link = {
+ * id: 1,
+ * source: { id: 2, source: 0, target: 0 },
+ * target: { id: 3, source: 4, target: 5 }
+ * };
+ *
+ * @example
+ * // Universal link with additional values
+ * const universalLink: Link = {
+ * id: 1,
+ * source: 2,
+ * target: 3,
+ * values: [4, 5, { id: 6, source: 7, target: 8 }]
+ * };
+ */
+export interface Link {
+ /**
+ * Unique identifier for this link.
+ */
+ readonly id: LinkId;
+
+ /**
+ * Reference to the source of this link.
+ * Can be a LinkId for direct reference or a nested Link for inline definition.
+ */
+ readonly source: LinkRef;
+
+ /**
+ * Reference to the target of this link.
+ * Can be a LinkId for direct reference or a nested Link for inline definition.
+ */
+ readonly target: LinkRef;
+
+ /**
+ * Optional additional values for universal links.
+ * Supports arbitrary number of references beyond source/target.
+ * Compatible with links-notation's values array model.
+ */
+ readonly values?: readonly LinkRef[];
+}
+
+// =============================================================================
+// Pattern Matching Types
+// =============================================================================
+
+/**
+ * Special value indicating "any" match in pattern queries.
+ * When used in a LinkPattern, matches any value in that position.
+ */
+export const Any = Symbol('Any');
+export type AnyType = typeof Any;
+
+/**
+ * Pattern for matching links in queries.
+ *
+ * Uses `Any` symbol as a wildcard to match any value in that position.
+ * Each field can be:
+ * - A specific LinkRef value (exact match)
+ * - The `Any` symbol (matches anything)
+ * - undefined (not included in match criteria)
+ *
+ * @example
+ * // Match all links with source = 5
+ * const pattern: LinkPattern = { source: 5 };
+ *
+ * // Match all links with any source and target = 10
+ * const pattern2: LinkPattern = { source: Any, target: 10 };
+ *
+ * // Match all links (equivalent to no filter)
+ * const pattern3: LinkPattern = { source: Any, target: Any };
+ */
+export interface LinkPattern {
+ /**
+ * Match criteria for link ID.
+ */
+ readonly id?: LinkId | AnyType;
+
+ /**
+ * Match criteria for link source.
+ */
+ readonly source?: LinkRef | AnyType;
+
+ /**
+ * Match criteria for link target.
+ */
+ readonly target?: LinkRef | AnyType;
+}
+
+// =============================================================================
+// LinkStore Interface
+// =============================================================================
+
+/**
+ * Interface for link storage operations.
+ *
+ * Provides CRUD operations for managing links, plus query capabilities
+ * with pattern matching support. This is the main interface that
+ * implementations must satisfy.
+ *
+ * The design follows the doublets-rs trait pattern while maintaining
+ * JavaScript/TypeScript idioms.
+ *
+ * @example
+ * class InMemoryLinkStore implements LinkStore {
+ * // ... implementation
+ * }
+ *
+ * const store: LinkStore = new InMemoryLinkStore();
+ * const link = await store.create(1, 2);
+ * console.log(link.id); // auto-generated ID
+ */
+export interface LinkStore {
+ // ---------------------------------------------------------------------------
+ // Create Operations
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Creates a new link from source to target.
+ *
+ * The implementation should auto-generate a unique ID for the link.
+ *
+ * @param source - The source reference for the new link
+ * @param target - The target reference for the new link
+ * @returns The newly created Link with its assigned ID
+ *
+ * @example
+ * const link = await store.create(1, 2);
+ * console.log(link); // { id: 1, source: 1, target: 2 }
+ */
+ create(source: LinkRef, target: LinkRef): Promise;
+
+ /**
+ * Creates a new link with additional values (universal link).
+ *
+ * @param source - The source reference for the new link
+ * @param target - The target reference for the new link
+ * @param values - Additional value references
+ * @returns The newly created Link
+ */
+ createWithValues(
+ source: LinkRef,
+ target: LinkRef,
+ values: readonly LinkRef[]
+ ): Promise;
+
+ // ---------------------------------------------------------------------------
+ // Read Operations
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Retrieves a link by its ID.
+ *
+ * @param id - The ID of the link to retrieve
+ * @returns The Link if found, null otherwise
+ *
+ * @example
+ * const link = await store.get(42);
+ * if (link) {
+ * console.log(`Found: ${link.source} -> ${link.target}`);
+ * }
+ */
+ get(id: LinkId): Promise;
+
+ /**
+ * Checks if a link with the given ID exists.
+ *
+ * @param id - The ID to check
+ * @returns True if the link exists, false otherwise
+ *
+ * @example
+ * if (await store.exists(42)) {
+ * console.log('Link 42 exists');
+ * }
+ */
+ exists(id: LinkId): Promise;
+
+ /**
+ * Finds links matching the given pattern.
+ *
+ * Supports wildcards via the `Any` symbol for flexible querying.
+ *
+ * @param pattern - The pattern to match against
+ * @returns Array of matching links
+ *
+ * @example
+ * // Find all links with source = 5
+ * const links = await store.find({ source: 5 });
+ *
+ * // Find all links with any source, target = 10
+ * const links2 = await store.find({ source: Any, target: 10 });
+ */
+ find(pattern: LinkPattern): Promise;
+
+ /**
+ * Counts links matching the given pattern.
+ *
+ * If no pattern is provided, counts all links.
+ *
+ * @param pattern - Optional pattern to match against
+ * @returns Count of matching links
+ *
+ * @example
+ * const total = await store.count();
+ * const withSource5 = await store.count({ source: 5 });
+ */
+ count(pattern?: LinkPattern): Promise;
+
+ // ---------------------------------------------------------------------------
+ // Update Operations
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Updates an existing link's source and/or target.
+ *
+ * @param id - The ID of the link to update
+ * @param source - New source reference
+ * @param target - New target reference
+ * @returns The updated Link
+ * @throws If the link with given ID does not exist
+ *
+ * @example
+ * const updated = await store.update(1, 10, 20);
+ * console.log(updated); // { id: 1, source: 10, target: 20 }
+ */
+ update(id: LinkId, source: LinkRef, target: LinkRef): Promise;
+
+ // ---------------------------------------------------------------------------
+ // Delete Operations
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Deletes a link by its ID.
+ *
+ * @param id - The ID of the link to delete
+ * @returns True if the link was deleted, false if it didn't exist
+ *
+ * @example
+ * const deleted = await store.delete(42);
+ * if (deleted) {
+ * console.log('Link 42 was deleted');
+ * }
+ */
+ delete(id: LinkId): Promise;
+
+ /**
+ * Deletes all links matching the given pattern.
+ *
+ * @param pattern - The pattern to match for deletion
+ * @returns Count of deleted links
+ *
+ * @example
+ * // Delete all links with source = 5
+ * const count = await store.deleteMatching({ source: 5 });
+ * console.log(`Deleted ${count} links`);
+ */
+ deleteMatching(pattern: LinkPattern): Promise;
+
+ // ---------------------------------------------------------------------------
+ // Iteration Operations
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Iterates over all links matching the given pattern.
+ *
+ * Provides an async iterator for memory-efficient traversal of large datasets.
+ *
+ * @param pattern - Optional pattern to filter links
+ * @returns AsyncIterable of matching links
+ *
+ * @example
+ * for await (const link of store.iterate({ source: 5 })) {
+ * console.log(link);
+ * }
+ */
+ iterate(pattern?: LinkPattern): AsyncIterable;
+}
+
+// =============================================================================
+// Utility Types
+// =============================================================================
+
+/**
+ * Extracts the ID type from a Link type.
+ */
+export type LinkIdOf = L['id'];
+
+/**
+ * A mutable version of Link for internal use.
+ */
+export interface MutableLink {
+ id: LinkId;
+ source: LinkRef;
+ target: LinkRef;
+ values?: LinkRef[];
+}
+
+/**
+ * Options for creating a new link.
+ */
+export interface CreateLinkOptions {
+ /**
+ * The source reference for the new link.
+ */
+ source: LinkRef;
+
+ /**
+ * The target reference for the new link.
+ */
+ target: LinkRef;
+
+ /**
+ * Optional additional values for universal links.
+ */
+ values?: LinkRef[];
+}
+
+/**
+ * Result type for operations that might fail.
+ */
+export type LinkResult =
+ | { success: true; value: T }
+ | { success: false; error: string };
+
+// =============================================================================
+// Re-exports for convenience
+// =============================================================================
+
+export type { Link as ILink } from './types.js';
diff --git a/js/tests/index.test.js b/js/tests/index.test.js
index 15fbcb6..030855f 100644
--- a/js/tests/index.test.js
+++ b/js/tests/index.test.js
@@ -1,12 +1,249 @@
/**
- * Example test file using test-anywhere
- * Works with Node.js, Bun, and Deno
+ * Test file for links-queue
+ * Works with Node.js, Bun, and Deno via test-anywhere
*/
import { describe, it, expect } from 'test-anywhere';
-import { add, multiply } from '../src/index.js';
+import {
+ add,
+ multiply,
+ Any,
+ isLink,
+ isLinkId,
+ isLinkRef,
+ getLinkId,
+ createLink,
+ matchesPattern,
+} from '../src/index.js';
-describe('add function', () => {
+// =============================================================================
+// Link and LinkRef Tests
+// =============================================================================
+
+describe('Link creation', () => {
+ it('should create a simple link with createLink', () => {
+ const link = createLink(1, 2, 3);
+ expect(link.id).toBe(1);
+ expect(link.source).toBe(2);
+ expect(link.target).toBe(3);
+ });
+
+ it('should create a link with bigint IDs', () => {
+ const link = createLink(1n, 2n, 3n);
+ expect(link.id).toBe(1n);
+ expect(link.source).toBe(2n);
+ expect(link.target).toBe(3n);
+ });
+
+ it('should create a link with string IDs', () => {
+ const link = createLink('link-1', 'source-id', 'target-id');
+ expect(link.id).toBe('link-1');
+ expect(link.source).toBe('source-id');
+ expect(link.target).toBe('target-id');
+ });
+
+ it('should create a link with nested link as source', () => {
+ const inner = createLink(2, 3, 4);
+ const outer = createLink(1, inner, 5);
+ expect(outer.id).toBe(1);
+ expect(outer.source).toEqual(inner);
+ expect(outer.target).toBe(5);
+ });
+
+ it('should create a universal link with values', () => {
+ const link = createLink(1, 2, 3, [4, 5, 6]);
+ expect(link.id).toBe(1);
+ expect(link.source).toBe(2);
+ expect(link.target).toBe(3);
+ expect(link.values).toEqual([4, 5, 6]);
+ });
+
+ it('should not include values property if values array is empty', () => {
+ const link = createLink(1, 2, 3, []);
+ expect(link.values).toBeUndefined();
+ });
+
+ it('should freeze the created link', () => {
+ const link = createLink(1, 2, 3);
+ expect(Object.isFrozen(link)).toBe(true);
+ });
+});
+
+describe('Deeply nested links', () => {
+ it('should support arbitrary nesting levels', () => {
+ const level3 = createLink(3, 30, 31);
+ const level2 = createLink(2, level3, 21);
+ const level1 = createLink(1, level2, 11);
+
+ expect(level1.id).toBe(1);
+ expect(level1.source.id).toBe(2);
+ expect(level1.source.source.id).toBe(3);
+ expect(level1.source.source.source).toBe(30);
+ expect(level1.source.source.target).toBe(31);
+ });
+});
+
+// =============================================================================
+// Type Check Functions Tests
+// =============================================================================
+
+describe('isLink', () => {
+ it('should return true for valid Link objects', () => {
+ expect(isLink({ id: 1, source: 2, target: 3 })).toBe(true);
+ expect(isLink(createLink(1, 2, 3))).toBe(true);
+ });
+
+ it('should return false for non-Link values', () => {
+ expect(isLink(null)).toBe(false);
+ expect(isLink(undefined)).toBe(false);
+ expect(isLink(42)).toBe(false);
+ expect(isLink('link')).toBe(false);
+ expect(isLink({})).toBe(false);
+ expect(isLink({ id: 1 })).toBe(false);
+ expect(isLink({ id: 1, source: 2 })).toBe(false);
+ });
+});
+
+describe('isLinkId', () => {
+ it('should return true for valid LinkId types', () => {
+ expect(isLinkId(42)).toBe(true);
+ expect(isLinkId(0)).toBe(true);
+ expect(isLinkId(-1)).toBe(true);
+ expect(isLinkId(9007199254740993n)).toBe(true);
+ expect(isLinkId('uuid-here')).toBe(true);
+ expect(isLinkId('')).toBe(true);
+ });
+
+ it('should return false for non-LinkId values', () => {
+ expect(isLinkId(null)).toBe(false);
+ expect(isLinkId(undefined)).toBe(false);
+ expect(isLinkId({})).toBe(false);
+ expect(isLinkId([])).toBe(false);
+ expect(isLinkId(Symbol('test'))).toBe(false);
+ });
+});
+
+describe('isLinkRef', () => {
+ it('should return true for LinkId values', () => {
+ expect(isLinkRef(42)).toBe(true);
+ expect(isLinkRef(1n)).toBe(true);
+ expect(isLinkRef('id')).toBe(true);
+ });
+
+ it('should return true for Link objects', () => {
+ expect(isLinkRef({ id: 1, source: 2, target: 3 })).toBe(true);
+ expect(isLinkRef(createLink(1, 2, 3))).toBe(true);
+ });
+
+ it('should return false for invalid values', () => {
+ expect(isLinkRef(null)).toBe(false);
+ expect(isLinkRef({})).toBe(false);
+ });
+});
+
+describe('getLinkId', () => {
+ it('should return the ID directly for LinkId values', () => {
+ expect(getLinkId(42)).toBe(42);
+ expect(getLinkId(1n)).toBe(1n);
+ expect(getLinkId('uuid')).toBe('uuid');
+ });
+
+ it('should extract ID from Link objects', () => {
+ const link = createLink(99, 2, 3);
+ expect(getLinkId(link)).toBe(99);
+ });
+
+ it('should extract ID from nested Link objects', () => {
+ const inner = createLink(10, 20, 30);
+ expect(getLinkId(inner)).toBe(10);
+ });
+});
+
+// =============================================================================
+// Pattern Matching Tests
+// =============================================================================
+
+describe('matchesPattern', () => {
+ const link = createLink(1, 5, 10);
+
+ it('should match when pattern is empty', () => {
+ expect(matchesPattern(link, {})).toBe(true);
+ });
+
+ it('should match by id', () => {
+ expect(matchesPattern(link, { id: 1 })).toBe(true);
+ expect(matchesPattern(link, { id: 2 })).toBe(false);
+ });
+
+ it('should match by source', () => {
+ expect(matchesPattern(link, { source: 5 })).toBe(true);
+ expect(matchesPattern(link, { source: 99 })).toBe(false);
+ });
+
+ it('should match by target', () => {
+ expect(matchesPattern(link, { target: 10 })).toBe(true);
+ expect(matchesPattern(link, { target: 99 })).toBe(false);
+ });
+
+ it('should match multiple fields', () => {
+ expect(matchesPattern(link, { source: 5, target: 10 })).toBe(true);
+ expect(matchesPattern(link, { source: 5, target: 99 })).toBe(false);
+ expect(matchesPattern(link, { id: 1, source: 5, target: 10 })).toBe(true);
+ });
+
+ it('should match Any wildcard for source', () => {
+ expect(matchesPattern(link, { source: Any })).toBe(true);
+ expect(matchesPattern(link, { source: Any, target: 10 })).toBe(true);
+ expect(matchesPattern(link, { source: Any, target: 99 })).toBe(false);
+ });
+
+ it('should match Any wildcard for target', () => {
+ expect(matchesPattern(link, { target: Any })).toBe(true);
+ expect(matchesPattern(link, { source: 5, target: Any })).toBe(true);
+ expect(matchesPattern(link, { source: 99, target: Any })).toBe(false);
+ });
+
+ it('should match Any wildcard for id', () => {
+ expect(matchesPattern(link, { id: Any })).toBe(true);
+ expect(matchesPattern(link, { id: Any, source: 5 })).toBe(true);
+ });
+
+ it('should match all with Any wildcards', () => {
+ expect(matchesPattern(link, { id: Any, source: Any, target: Any })).toBe(
+ true
+ );
+ });
+
+ it('should match nested link reference by ID', () => {
+ const inner = createLink(5, 50, 51);
+ const outer = createLink(1, inner, 10);
+
+ // Pattern matches source by extracting its ID
+ expect(matchesPattern(outer, { source: 5 })).toBe(true);
+ expect(matchesPattern(outer, { source: inner })).toBe(true);
+ });
+});
+
+// =============================================================================
+// Any Symbol Tests
+// =============================================================================
+
+describe('Any symbol', () => {
+ it('should be a unique symbol', () => {
+ expect(typeof Any).toBe('symbol');
+ expect(Any.toString()).toBe('Symbol(Any)');
+ });
+
+ it('should be referentially equal to itself', () => {
+ expect(Any === Any).toBe(true);
+ });
+});
+
+// =============================================================================
+// Backward Compatibility Tests (deprecated functions)
+// =============================================================================
+
+describe('add function (deprecated)', () => {
it('should add two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
@@ -20,7 +257,7 @@ describe('add function', () => {
});
});
-describe('multiply function', () => {
+describe('multiply function (deprecated)', () => {
it('should multiply two positive numbers', () => {
expect(multiply(2, 3)).toBe(6);
});
diff --git a/rust/examples/basic_usage.rs b/rust/examples/basic_usage.rs
index 6f7c345..4088d1c 100644
--- a/rust/examples/basic_usage.rs
+++ b/rust/examples/basic_usage.rs
@@ -1,34 +1,166 @@
//! Basic usage example for links-queue.
//!
-//! This example demonstrates the basic functionality of the package.
+//! This example demonstrates the core Link and `LinkStore` interfaces.
//!
//! Run with: `cargo run --example basic_usage`
-use links_queue::{add, delay, multiply};
-
-#[tokio::main]
-async fn main() {
- // Example 1: Basic arithmetic
- println!("Example 1: Basic arithmetic");
- println!("2 + 3 = {}", add(2, 3));
- println!("2 * 3 = {}", multiply(2, 3));
- println!();
-
- // Example 2: Working with larger numbers
- println!("Example 2: Working with larger numbers");
- println!("1000 + 2000 = {}", add(1000, 2000));
- println!("100 * 200 = {}", multiply(100, 200));
- println!();
-
- // Example 3: Working with negative numbers
- println!("Example 3: Working with negative numbers");
- println!("-5 + 10 = {}", add(-5, 10));
- println!("-3 * 4 = {}", multiply(-3, 4));
- println!();
-
- // Example 4: Async delay
- println!("Example 4: Async delay");
- println!("Waiting for 1 second...");
- delay(1.0).await;
- println!("Done!");
+#![allow(clippy::uninlined_format_args)]
+#![allow(clippy::redundant_clone)]
+#![allow(clippy::useless_vec)]
+#![allow(clippy::needless_collect)]
+
+use links_queue::{Any, Link, LinkPattern, LinkRef};
+
+fn main() {
+ // =========================================================================
+ // Creating Links
+ // =========================================================================
+
+ println!("=== Creating Links ===\n");
+
+ // Simple link with u64 IDs
+ let simple_link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ println!("Simple link: {:?}", simple_link);
+
+ // Link with u32 IDs (smaller memory footprint)
+ let u32_link = Link::new(1u32, LinkRef::Id(2u32), LinkRef::Id(3u32));
+ println!("u32 link: {:?}", u32_link);
+
+ // Link with usize IDs (platform-native size)
+ let usize_link = Link::new(1usize, LinkRef::Id(2usize), LinkRef::Id(3usize));
+ println!("usize link: {:?}", usize_link);
+
+ // =========================================================================
+ // Nested Links (Recursive Structures)
+ // =========================================================================
+
+ println!("\n=== Nested Links ===\n");
+
+ // Create nested link structures for complex relationships
+ let inner = Link::new(10u64, LinkRef::Id(20), LinkRef::Id(30));
+ let outer = Link::new(1u64, LinkRef::link(inner.clone()), LinkRef::Id(5));
+ println!("Outer link with nested source: {:?}", outer);
+ println!(" Outer source ID: {}", outer.source_id()); // 10
+
+ // Deep nesting: (1: (2: (3: 30 31)))
+ let level3 = Link::new(3u64, LinkRef::Id(30), LinkRef::Id(31));
+ let level2 = Link::new(2u64, LinkRef::link(level3), LinkRef::Id(21));
+ let level1 = Link::new(1u64, LinkRef::link(level2), LinkRef::Id(11));
+ println!("Deeply nested: {:?}", level1);
+ println!(" Level 1 source ID: {}", level1.source_id()); // 2
+
+ // =========================================================================
+ // Universal Links (Variable Number of References)
+ // =========================================================================
+
+ println!("\n=== Universal Links ===\n");
+
+ // Universal link with additional values beyond source/target
+ let universal = Link::with_values(
+ 100u64,
+ LinkRef::Id(1), // subject
+ LinkRef::Id(2), // predicate
+ vec![LinkRef::Id(3), LinkRef::Id(4), LinkRef::Id(5)], // objects
+ );
+ println!("Universal link: {:?}", universal);
+ println!(" Has values: {}", universal.has_values());
+
+ // =========================================================================
+ // Special Link Types
+ // =========================================================================
+
+ println!("\n=== Special Link Types ===\n");
+
+ // Point link (self-referential: id == source == target)
+ let point = Link::::point(42);
+ println!("Point link: {:?}", point);
+ println!(" Is point: {}", point.is_point()); // true
+
+ // Null/nothing link (all zeros)
+ let null_link = Link::::nothing();
+ println!("Null link: {:?}", null_link);
+ println!(" Is null: {}", null_link.is_null()); // true
+
+ // =========================================================================
+ // LinkRef Operations
+ // =========================================================================
+
+ println!("\n=== LinkRef Operations ===\n");
+
+ // Create refs from IDs
+ let ref_id: LinkRef = LinkRef::Id(42);
+ println!("ID ref: {:?}", ref_id);
+ println!(" Is ID: {}", ref_id.is_id()); // true
+ println!(" Get ID: {}", ref_id.get_id()); // 42
+
+ // Create refs from links
+ let ref_link: LinkRef = LinkRef::link(simple_link.clone());
+ println!("Link ref: {:?}", ref_link);
+ println!(" Is link: {}", ref_link.is_link()); // true
+ println!(" Get ID: {}", ref_link.get_id()); // 1 (the link's ID)
+
+ // Using From trait
+ let from_id: LinkRef = 99u64.into();
+ let from_link: LinkRef = simple_link.clone().into();
+ println!("From ID: {:?}", from_id);
+ println!("From link: {:?}", from_link);
+
+ // =========================================================================
+ // Pattern Matching
+ // =========================================================================
+
+ println!("\n=== Pattern Matching ===\n");
+
+ let links = vec![
+ Link::new(1u64, LinkRef::Id(10), LinkRef::Id(20)),
+ Link::new(2u64, LinkRef::Id(10), LinkRef::Id(30)),
+ Link::new(3u64, LinkRef::Id(20), LinkRef::Id(30)),
+ Link::new(4u64, LinkRef::Id(30), LinkRef::Id(40)),
+ ];
+
+ // Match all links
+ let all_pattern = LinkPattern::::all();
+ let all_matched: Vec<_> = links.iter().filter(|l| all_pattern.matches(l)).collect();
+ println!("All links count: {}", all_matched.len()); // 4
+
+ // Match by source
+ let source_pattern = LinkPattern::with_source(LinkRef::Id(10u64));
+ let with_source_10: Vec<_> = links
+ .iter()
+ .filter(|l| source_pattern.matches(l))
+ .map(|l| l.id)
+ .collect();
+ println!("Links with source=10: {:?}", with_source_10); // [1, 2]
+
+ // Match by target
+ let target_pattern = LinkPattern::with_target(LinkRef::Id(30u64));
+ let with_target_30: Vec<_> = links
+ .iter()
+ .filter(|l| target_pattern.matches(l))
+ .map(|l| l.id)
+ .collect();
+ println!("Links with target=30: {:?}", with_target_30); // [2, 3]
+
+ // Match with Any wildcard (builder pattern)
+ let any_source_pattern = LinkPattern::::new().source(Any).target(30u64);
+ let any_source_target_30: Vec<_> = links
+ .iter()
+ .filter(|l| any_source_pattern.matches(l))
+ .map(|l| l.id)
+ .collect();
+ println!(
+ "Links with Any source, target=30: {:?}",
+ any_source_target_30
+ ); // [2, 3]
+
+ // Match by source AND target
+ let exact_pattern = LinkPattern::with_source_target(LinkRef::Id(10u64), LinkRef::Id(20u64));
+ let exact_match: Vec<_> = links
+ .iter()
+ .filter(|l| exact_pattern.matches(l))
+ .map(|l| l.id)
+ .collect();
+ println!("Links with source=10, target=20: {:?}", exact_match); // [1]
+
+ println!("\n=== Done ===\n");
}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index befd884..e850502 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -1,10 +1,65 @@
-//! Example module entry point.
+//! links-queue - A lightweight message queue implementation.
//!
-//! Replace this with your actual implementation.
+//! This crate provides the core Link and `LinkStore` abstractions for
+//! building associative data structures and message queues.
+//!
+//! # Core Concepts
+//!
+//! - **Link**: A directed relationship from source to target, identified by a unique ID.
+//! - **`LinkStore`**: A storage backend for managing links with CRUD operations.
+//! - **`LinkPattern`**: A pattern for querying links with wildcard support.
+//!
+//! # Design Goals
+//!
+//! - Compatible with [links-notation](https://github.com/link-foundation/links-notation)
+//! - Compatible with [doublets-rs](https://github.com/linksplatform/doublets-rs) patterns
+//! - Extensible for universal links with any number of references
+//!
+//! # Example
+//!
+//! ```rust
+//! use links_queue::{Link, LinkRef, LinkPattern, Any};
+//!
+//! // Create a simple link
+//! let link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+//! assert_eq!(link.id, 1);
+//! assert_eq!(link.source_id(), 2);
+//! assert_eq!(link.target_id(), 3);
+//!
+//! // Create a nested link structure
+//! let inner = Link::new(2u64, LinkRef::Id(3), LinkRef::Id(4));
+//! let outer = Link::new(1u64, LinkRef::link(inner), LinkRef::Id(5));
+//!
+//! // Pattern matching
+//! let pattern = LinkPattern::with_source(LinkRef::Id(2u64));
+//! assert!(pattern.matches(&Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3))));
+//! ```
+
+// =============================================================================
+// Module Declarations
+// =============================================================================
+
+mod traits;
+
+// =============================================================================
+// Public Re-exports
+// =============================================================================
+
+pub use traits::{
+ Any, Link, LinkError, LinkPattern, LinkRef, LinkResult, LinkStore, LinkType, PatternField,
+};
+
+// =============================================================================
+// Package Version
+// =============================================================================
/// Package version (matches Cargo.toml version).
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
+// =============================================================================
+// Backward Compatible Exports (deprecated)
+// =============================================================================
+
/// Adds two numbers together.
///
/// # Arguments
@@ -22,6 +77,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// use links_queue::add;
/// assert_eq!(add(2, 3), 5);
/// ```
+#[deprecated(since = "0.2.0", note = "Use the Link and LinkStore traits instead")]
#[must_use]
pub const fn add(a: i64, b: i64) -> i64 {
a + b
@@ -44,6 +100,7 @@ pub const fn add(a: i64, b: i64) -> i64 {
/// use links_queue::multiply;
/// assert_eq!(multiply(2, 3), 6);
/// ```
+#[deprecated(since = "0.2.0", note = "Use the Link and LinkStore traits instead")]
#[must_use]
pub const fn multiply(a: i64, b: i64) -> i64 {
a * b
@@ -65,16 +122,68 @@ pub const fn multiply(a: i64, b: i64) -> i64 {
/// delay(0.1).await;
/// }
/// ```
+#[deprecated(since = "0.2.0", note = "Will be removed in future versions")]
pub async fn delay(seconds: f64) {
let duration = std::time::Duration::from_secs_f64(seconds);
tokio::time::sleep(duration).await;
}
+// =============================================================================
+// Unit Tests
+// =============================================================================
+
#[cfg(test)]
mod tests {
use super::*;
- mod add_tests {
+ mod link_api_tests {
+ use super::*;
+
+ #[test]
+ fn test_link_creation() {
+ let link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ assert_eq!(link.id, 1);
+ assert_eq!(link.source_id(), 2);
+ assert_eq!(link.target_id(), 3);
+ }
+
+ #[test]
+ fn test_link_with_values() {
+ let link = Link::with_values(
+ 1u64,
+ LinkRef::Id(2),
+ LinkRef::Id(3),
+ vec![LinkRef::Id(4), LinkRef::Id(5)],
+ );
+ assert!(link.has_values());
+ }
+
+ #[test]
+ fn test_nested_link() {
+ let inner = Link::new(2u64, LinkRef::Id(3), LinkRef::Id(4));
+ let outer = Link::new(1u64, LinkRef::link(inner), LinkRef::Id(5));
+ assert_eq!(outer.source_id(), 2);
+ }
+
+ #[test]
+ fn test_pattern_matching() {
+ let pattern = LinkPattern::with_source(LinkRef::Id(2u64));
+ let link1 = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ let link2 = Link::new(1u64, LinkRef::Id(5), LinkRef::Id(3));
+ assert!(pattern.matches(&link1));
+ assert!(!pattern.matches(&link2));
+ }
+
+ #[test]
+ fn test_any_pattern() {
+ let pattern = LinkPattern::::new().source(Any).target(3u64);
+ let link = Link::new(1u64, LinkRef::Id(999), LinkRef::Id(3));
+ assert!(pattern.matches(&link));
+ }
+ }
+
+ #[allow(deprecated)]
+ mod backward_compat_tests {
use super::*;
#[test]
@@ -96,10 +205,6 @@ mod tests {
fn test_add_large_numbers() {
assert_eq!(add(1_000_000, 2_000_000), 3_000_000);
}
- }
-
- mod multiply_tests {
- use super::*;
#[test]
fn test_multiply_positive_numbers() {
@@ -126,6 +231,7 @@ mod tests {
use super::*;
#[tokio::test]
+ #[allow(deprecated)]
async fn test_delay() {
let start = std::time::Instant::now();
delay(0.1).await;
diff --git a/rust/src/main.rs b/rust/src/main.rs
index 7ac37ba..f8be813 100644
--- a/rust/src/main.rs
+++ b/rust/src/main.rs
@@ -2,6 +2,8 @@
//!
//! This is a simple CLI that demonstrates the library functionality.
+#![allow(deprecated)]
+
use links_queue::{add, delay, multiply};
#[tokio::main]
diff --git a/rust/src/traits.rs b/rust/src/traits.rs
new file mode 100644
index 0000000..991e0ae
--- /dev/null
+++ b/rust/src/traits.rs
@@ -0,0 +1,908 @@
+//! Core Link and `LinkStore` trait definitions for links-queue.
+//!
+//! This module provides the foundational traits for the Link data model
+//! and `LinkStore` operations. These traits establish the API contract that
+//! implementations must follow.
+//!
+//! # Design Goals
+//!
+//! - Compatible with [links-notation](https://github.com/link-foundation/links-notation) (supports nested/recursive structures)
+//! - Compatible with [doublets-rs](https://github.com/linksplatform/doublets-rs) patterns (source/target model)
+//! - Extensible for universal links with any number of references
+//! - Generic over ID types for maximum flexibility
+//!
+//! # Example
+//!
+//! ```rust
+//! use links_queue::{Link, LinkStore, LinkRef, LinkPattern, Any};
+//!
+//! // Create a simple link
+//! let link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+//!
+//! // Pattern matching with wildcards
+//! let pattern = LinkPattern::with_source(LinkRef::Id(2));
+//! ```
+
+use std::fmt::Debug;
+use std::hash::Hash;
+
+// =============================================================================
+// ID Type Trait
+// =============================================================================
+
+/// Trait for types that can be used as link identifiers.
+///
+/// This trait defines the requirements for ID types, allowing the use of
+/// various numeric types (u32, u64, usize, etc.) as link identifiers.
+///
+/// # Implementors
+///
+/// This trait is automatically implemented for common numeric types:
+/// - `u8`, `u16`, `u32`, `u64`, `u128`, `usize`
+/// - `i8`, `i16`, `i32`, `i64`, `i128`, `isize`
+///
+/// Custom types can also implement this trait for use as link IDs.
+pub trait LinkType:
+ Copy + Clone + Default + Debug + PartialEq + Eq + Hash + Send + Sync + 'static
+{
+ /// Returns the zero/default value for this type.
+ fn zero() -> Self;
+
+ /// Checks if this value represents "nothing" (typically zero).
+ fn is_nothing(&self) -> bool;
+}
+
+// Implement LinkType for common numeric types
+macro_rules! impl_link_type {
+ ($($t:ty),*) => {
+ $(
+ impl LinkType for $t {
+ #[inline]
+ fn zero() -> Self {
+ 0
+ }
+
+ #[inline]
+ fn is_nothing(&self) -> bool {
+ *self == 0
+ }
+ }
+ )*
+ };
+}
+
+impl_link_type!(u8, u16, u32, u64, u128, usize);
+impl_link_type!(i8, i16, i32, i64, i128, isize);
+
+// =============================================================================
+// Link Reference Enum
+// =============================================================================
+
+/// A reference to a link, which can be an ID or a nested Link.
+///
+/// This enum supports flexible link compositions and nested structures,
+/// enabling arbitrary levels of recursion as required by links-notation.
+///
+/// # Type Parameters
+///
+/// * `T` - The link ID type (must implement [`LinkType`])
+///
+/// # Examples
+///
+/// ```rust
+/// use links_queue::{Link, LinkRef};
+///
+/// // Direct reference by ID
+/// let ref1: LinkRef = LinkRef::Id(42);
+///
+/// // Nested link reference
+/// let inner = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+/// let ref2: LinkRef = LinkRef::Link(Box::new(inner));
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum LinkRef {
+ /// Direct reference by link ID.
+ Id(T),
+ /// Nested link object (boxed to avoid infinite size).
+ Link(Box>),
+}
+
+impl LinkRef {
+ /// Creates a new ID reference.
+ #[inline]
+ pub const fn id(id: T) -> Self {
+ Self::Id(id)
+ }
+
+ /// Creates a new nested link reference.
+ #[inline]
+ pub fn link(link: Link) -> Self {
+ Self::Link(Box::new(link))
+ }
+
+ /// Returns the ID of this reference.
+ ///
+ /// If this is a nested link, returns the link's ID.
+ pub fn get_id(&self) -> T {
+ match self {
+ Self::Id(id) => *id,
+ Self::Link(link) => link.id,
+ }
+ }
+
+ /// Returns true if this is an ID reference.
+ #[inline]
+ pub const fn is_id(&self) -> bool {
+ matches!(self, Self::Id(_))
+ }
+
+ /// Returns true if this is a nested link reference.
+ #[inline]
+ pub const fn is_link(&self) -> bool {
+ matches!(self, Self::Link(_))
+ }
+
+ /// Returns the ID if this is an ID reference, None otherwise.
+ pub const fn as_id(&self) -> Option {
+ match self {
+ Self::Id(id) => Some(*id),
+ Self::Link(_) => None,
+ }
+ }
+
+ /// Returns a reference to the nested link if this is a link reference, None otherwise.
+ pub fn as_link(&self) -> Option<&Link> {
+ match self {
+ Self::Id(_) => None,
+ Self::Link(link) => Some(link),
+ }
+ }
+}
+
+impl From for LinkRef {
+ fn from(id: T) -> Self {
+ Self::Id(id)
+ }
+}
+
+impl From> for LinkRef {
+ fn from(link: Link) -> Self {
+ Self::Link(Box::new(link))
+ }
+}
+
+// =============================================================================
+// Link Struct
+// =============================================================================
+
+/// Represents a link (associative data structure) connecting source to target.
+///
+/// A Link is the fundamental unit of data in links-queue. It represents
+/// a directed relationship from a source to a target, identified by a unique ID.
+///
+/// For compatibility with links-notation's universal link model, additional
+/// values can be provided beyond the basic source/target pair.
+///
+/// # Type Parameters
+///
+/// * `T` - The link ID type (must implement [`LinkType`])
+///
+/// # Examples
+///
+/// ```rust
+/// use links_queue::{Link, LinkRef};
+///
+/// // Simple doublet-style link
+/// let link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+///
+/// // Nested link structure
+/// let inner = Link::new(2u64, LinkRef::Id(0), LinkRef::Id(0));
+/// let nested = Link::new(1u64, LinkRef::link(inner), LinkRef::Id(5));
+///
+/// // Universal link with additional values
+/// let universal = Link::with_values(
+/// 1u64,
+/// LinkRef::Id(2),
+/// LinkRef::Id(3),
+/// vec![LinkRef::Id(4), LinkRef::Id(5)]
+/// );
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Link {
+ /// Unique identifier for this link.
+ pub id: T,
+ /// Reference to the source of this link.
+ pub source: LinkRef,
+ /// Reference to the target of this link.
+ pub target: LinkRef,
+ /// Optional additional values for universal links.
+ pub values: Option>>,
+}
+
+impl Link {
+ /// Creates a new link with the given ID, source, and target.
+ ///
+ /// # Arguments
+ ///
+ /// * `id` - The unique identifier for this link
+ /// * `source` - Reference to the source
+ /// * `target` - Reference to the target
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use links_queue::{Link, LinkRef};
+ ///
+ /// let link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ /// assert_eq!(link.id, 1);
+ /// ```
+ #[inline]
+ pub const fn new(id: T, source: LinkRef, target: LinkRef) -> Self {
+ Self {
+ id,
+ source,
+ target,
+ values: None,
+ }
+ }
+
+ /// Creates a new link with additional values (universal link).
+ ///
+ /// # Arguments
+ ///
+ /// * `id` - The unique identifier for this link
+ /// * `source` - Reference to the source
+ /// * `target` - Reference to the target
+ /// * `values` - Additional value references
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use links_queue::{Link, LinkRef};
+ ///
+ /// let link = Link::with_values(
+ /// 1u64,
+ /// LinkRef::Id(2),
+ /// LinkRef::Id(3),
+ /// vec![LinkRef::Id(4), LinkRef::Id(5)]
+ /// );
+ /// assert!(link.values.is_some());
+ /// ```
+ #[inline]
+ pub fn with_values(
+ id: T,
+ source: LinkRef,
+ target: LinkRef,
+ values: Vec>,
+ ) -> Self {
+ Self {
+ id,
+ source,
+ target,
+ values: if values.is_empty() {
+ None
+ } else {
+ Some(values)
+ },
+ }
+ }
+
+ /// Creates a "point" link where id == source == target.
+ ///
+ /// A point link represents a self-referential entity.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use links_queue::Link;
+ ///
+ /// let point = Link::::point(42);
+ /// assert_eq!(point.source.get_id(), 42);
+ /// assert_eq!(point.target.get_id(), 42);
+ /// ```
+ #[inline]
+ #[must_use]
+ pub const fn point(id: T) -> Self {
+ Self::new(id, LinkRef::Id(id), LinkRef::Id(id))
+ }
+
+ /// Creates a "null" link with all fields set to zero/default.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use links_queue::Link;
+ ///
+ /// let null_link = Link::::nothing();
+ /// assert!(null_link.is_null());
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn nothing() -> Self {
+ let zero = T::zero();
+ Self::new(zero, LinkRef::Id(zero), LinkRef::Id(zero))
+ }
+
+ /// Returns true if this link is a "point" (id == source == target).
+ pub fn is_point(&self) -> bool {
+ self.source.get_id() == self.id && self.target.get_id() == self.id
+ }
+
+ /// Returns true if this is a null link (all fields are zero/nothing).
+ pub fn is_null(&self) -> bool {
+ self.id.is_nothing()
+ && self.source.get_id().is_nothing()
+ && self.target.get_id().is_nothing()
+ }
+
+ /// Returns true if this link has additional values.
+ pub fn has_values(&self) -> bool {
+ self.values.as_ref().is_some_and(|v| !v.is_empty())
+ }
+
+ /// Returns the source ID.
+ pub fn source_id(&self) -> T {
+ self.source.get_id()
+ }
+
+ /// Returns the target ID.
+ pub fn target_id(&self) -> T {
+ self.target.get_id()
+ }
+}
+
+impl Default for Link {
+ fn default() -> Self {
+ Self::nothing()
+ }
+}
+
+// =============================================================================
+// Pattern Matching
+// =============================================================================
+
+/// Represents "any" value in pattern matching.
+///
+/// Use this in [`LinkPattern`] to match any value in that position.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
+pub struct Any;
+
+/// A pattern field that can match a specific value or any value.
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
+pub enum PatternField {
+ /// Match any value.
+ #[default]
+ Any,
+ /// Match a specific value.
+ Value(LinkRef),
+}
+
+impl PatternField {
+ /// Returns true if this field matches the given reference.
+ pub fn matches(&self, reference: &LinkRef) -> bool {
+ match self {
+ Self::Any => true,
+ Self::Value(pattern_ref) => pattern_ref.get_id() == reference.get_id(),
+ }
+ }
+}
+
+impl From> for PatternField {
+ fn from(reference: LinkRef) -> Self {
+ Self::Value(reference)
+ }
+}
+
+impl From for PatternField {
+ fn from(id: T) -> Self {
+ Self::Value(LinkRef::Id(id))
+ }
+}
+
+impl From for PatternField {
+ fn from(_: Any) -> Self {
+ Self::Any
+ }
+}
+
+/// Pattern for matching links in queries.
+///
+/// Each field can be set to match a specific value or use [`Any`] as a wildcard.
+/// Fields set to [`PatternField::Any`] will match any value in that position.
+///
+/// # Examples
+///
+/// ```rust
+/// use links_queue::{LinkPattern, LinkRef, Any};
+///
+/// // Match all links with source = 5
+/// let pattern = LinkPattern::::with_source(LinkRef::Id(5));
+///
+/// // Match all links with any source and target = 10
+/// let pattern2 = LinkPattern::::new()
+/// .source(Any)
+/// .target(10u64);
+///
+/// // Match all links (equivalent to no filter)
+/// let pattern3 = LinkPattern::::all();
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
+pub struct LinkPattern {
+ /// Match criteria for link ID.
+ pub id: PatternField,
+ /// Match criteria for link source.
+ pub source: PatternField,
+ /// Match criteria for link target.
+ pub target: PatternField,
+}
+
+impl LinkPattern {
+ /// Creates a new empty pattern (matches all links).
+ #[inline]
+ #[must_use]
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Creates a pattern that matches all links.
+ #[inline]
+ #[must_use]
+ pub fn all() -> Self {
+ Self::default()
+ }
+
+ /// Creates a pattern that matches links with the given source.
+ pub const fn with_source(source: LinkRef) -> Self {
+ Self {
+ id: PatternField::Any,
+ source: PatternField::Value(source),
+ target: PatternField::Any,
+ }
+ }
+
+ /// Creates a pattern that matches links with the given target.
+ pub const fn with_target(target: LinkRef) -> Self {
+ Self {
+ id: PatternField::Any,
+ source: PatternField::Any,
+ target: PatternField::Value(target),
+ }
+ }
+
+ /// Creates a pattern that matches links with the given source and target.
+ pub const fn with_source_target(source: LinkRef, target: LinkRef) -> Self {
+ Self {
+ id: PatternField::Any,
+ source: PatternField::Value(source),
+ target: PatternField::Value(target),
+ }
+ }
+
+ /// Sets the ID pattern field (builder pattern).
+ #[must_use]
+ pub fn id(mut self, id: impl Into>) -> Self {
+ self.id = id.into();
+ self
+ }
+
+ /// Sets the source pattern field (builder pattern).
+ #[must_use]
+ pub fn source(mut self, source: impl Into>) -> Self {
+ self.source = source.into();
+ self
+ }
+
+ /// Sets the target pattern field (builder pattern).
+ #[must_use]
+ pub fn target(mut self, target: impl Into>) -> Self {
+ self.target = target.into();
+ self
+ }
+
+ /// Checks if this pattern matches the given link.
+ pub fn matches(&self, link: &Link) -> bool {
+ self.id.matches(&LinkRef::Id(link.id))
+ && self.source.matches(&link.source)
+ && self.target.matches(&link.target)
+ }
+}
+
+// =============================================================================
+// Error Types
+// =============================================================================
+
+/// Errors that can occur during link store operations.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum LinkError {
+ /// The requested link does not exist.
+ NotFound(T),
+ /// A link with this ID already exists.
+ AlreadyExists(T),
+ /// The link cannot be deleted because it has usages.
+ HasUsages(Vec>),
+ /// Storage limit has been reached.
+ LimitReached,
+ /// A custom error with a message.
+ Other(String),
+}
+
+impl std::fmt::Display for LinkError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::NotFound(id) => write!(f, "Link with ID {id:?} not found"),
+ Self::AlreadyExists(id) => write!(f, "Link with ID {id:?} already exists"),
+ Self::HasUsages(links) => write!(f, "Link has {} usages", links.len()),
+ Self::LimitReached => write!(f, "Storage limit reached"),
+ Self::Other(msg) => write!(f, "{msg}"),
+ }
+ }
+}
+
+impl std::error::Error for LinkError {}
+
+/// Result type for link store operations.
+pub type LinkResult = Result>;
+
+// =============================================================================
+// LinkStore Trait
+// =============================================================================
+
+/// Trait for link storage operations.
+///
+/// Provides CRUD operations for managing links, plus query capabilities
+/// with pattern matching support. This is the main trait that
+/// implementations must satisfy.
+///
+/// The design follows the doublets-rs trait pattern while maintaining
+/// Rust idioms and safety guarantees.
+///
+/// # Type Parameters
+///
+/// * `T` - The link ID type (must implement [`LinkType`])
+///
+/// # Example Implementation
+///
+/// ```rust,ignore
+/// use links_queue::{LinkStore, Link, LinkRef, LinkPattern, LinkResult, LinkError};
+///
+/// struct InMemoryLinkStore {
+/// links: std::collections::HashMap>,
+/// next_id: T,
+/// }
+///
+/// impl LinkStore for InMemoryLinkStore {
+/// // ... implementation
+/// }
+/// ```
+pub trait LinkStore: Send + Sync {
+ // -------------------------------------------------------------------------
+ // Create Operations
+ // -------------------------------------------------------------------------
+
+ /// Creates a new link from source to target.
+ ///
+ /// The implementation should auto-generate a unique ID for the link.
+ ///
+ /// # Arguments
+ ///
+ /// * `source` - The source reference for the new link
+ /// * `target` - The target reference for the new link
+ ///
+ /// # Returns
+ ///
+ /// The newly created Link with its assigned ID.
+ fn create(&mut self, source: LinkRef, target: LinkRef) -> LinkResult>;
+
+ /// Creates a new link with additional values (universal link).
+ ///
+ /// # Arguments
+ ///
+ /// * `source` - The source reference for the new link
+ /// * `target` - The target reference for the new link
+ /// * `values` - Additional value references
+ ///
+ /// # Returns
+ ///
+ /// The newly created Link with its assigned ID.
+ fn create_with_values(
+ &mut self,
+ source: LinkRef,
+ target: LinkRef,
+ values: Vec>,
+ ) -> LinkResult>;
+
+ // -------------------------------------------------------------------------
+ // Read Operations
+ // -------------------------------------------------------------------------
+
+ /// Retrieves a link by its ID.
+ ///
+ /// # Arguments
+ ///
+ /// * `id` - The ID of the link to retrieve
+ ///
+ /// # Returns
+ ///
+ /// The Link if found, None otherwise.
+ fn get(&self, id: T) -> Option<&Link>;
+
+ /// Checks if a link with the given ID exists.
+ ///
+ /// # Arguments
+ ///
+ /// * `id` - The ID to check
+ ///
+ /// # Returns
+ ///
+ /// True if the link exists, false otherwise.
+ fn exists(&self, id: T) -> bool {
+ self.get(id).is_some()
+ }
+
+ /// Finds links matching the given pattern.
+ ///
+ /// Supports wildcards via the [`Any`] pattern for flexible querying.
+ ///
+ /// # Arguments
+ ///
+ /// * `pattern` - The pattern to match against
+ ///
+ /// # Returns
+ ///
+ /// Vector of references to matching links.
+ fn find(&self, pattern: &LinkPattern) -> Vec<&Link>;
+
+ /// Counts links matching the given pattern.
+ ///
+ /// # Arguments
+ ///
+ /// * `pattern` - The pattern to match against
+ ///
+ /// # Returns
+ ///
+ /// Count of matching links.
+ fn count(&self, pattern: &LinkPattern) -> usize {
+ self.find(pattern).len()
+ }
+
+ /// Returns the total number of links in the store.
+ fn total_count(&self) -> usize {
+ self.count(&LinkPattern::all())
+ }
+
+ // -------------------------------------------------------------------------
+ // Update Operations
+ // -------------------------------------------------------------------------
+
+ /// Updates an existing link's source and/or target.
+ ///
+ /// # Arguments
+ ///
+ /// * `id` - The ID of the link to update
+ /// * `source` - New source reference
+ /// * `target` - New target reference
+ ///
+ /// # Returns
+ ///
+ /// The updated Link, or an error if the link doesn't exist.
+ fn update(&mut self, id: T, source: LinkRef, target: LinkRef) -> LinkResult>;
+
+ // -------------------------------------------------------------------------
+ // Delete Operations
+ // -------------------------------------------------------------------------
+
+ /// Deletes a link by its ID.
+ ///
+ /// # Arguments
+ ///
+ /// * `id` - The ID of the link to delete
+ ///
+ /// # Returns
+ ///
+ /// True if the link was deleted, false if it didn't exist.
+ fn delete(&mut self, id: T) -> bool;
+
+ /// Deletes all links matching the given pattern.
+ ///
+ /// # Arguments
+ ///
+ /// * `pattern` - The pattern to match for deletion
+ ///
+ /// # Returns
+ ///
+ /// Count of deleted links.
+ fn delete_matching(&mut self, pattern: &LinkPattern) -> usize;
+
+ // -------------------------------------------------------------------------
+ // Iteration Operations
+ // -------------------------------------------------------------------------
+
+ /// Returns an iterator over all links matching the given pattern.
+ ///
+ /// # Arguments
+ ///
+ /// * `pattern` - The pattern to filter links
+ ///
+ /// # Returns
+ ///
+ /// Iterator of references to matching links.
+ fn iter(&self, pattern: &LinkPattern) -> Box> + '_>;
+
+ /// Returns an iterator over all links.
+ fn iter_all(&self) -> Box> + '_> {
+ self.iter(&LinkPattern::all())
+ }
+}
+
+// =============================================================================
+// Tests
+// =============================================================================
+
+#[cfg(test)]
+#[allow(clippy::redundant_clone)]
+mod tests {
+ use super::*;
+
+ mod link_type_tests {
+ use super::*;
+
+ #[test]
+ fn test_link_type_zero() {
+ assert_eq!(u64::zero(), 0u64);
+ assert_eq!(i32::zero(), 0i32);
+ }
+
+ #[test]
+ fn test_link_type_is_nothing() {
+ assert!(0u64.is_nothing());
+ assert!(!1u64.is_nothing());
+ assert!(0i32.is_nothing());
+ assert!(!(-1i32).is_nothing());
+ }
+ }
+
+ mod link_ref_tests {
+ use super::*;
+
+ #[test]
+ fn test_link_ref_id() {
+ let ref_id: LinkRef = LinkRef::Id(42);
+ assert!(ref_id.is_id());
+ assert!(!ref_id.is_link());
+ assert_eq!(ref_id.get_id(), 42);
+ assert_eq!(ref_id.as_id(), Some(42));
+ assert!(ref_id.as_link().is_none());
+ }
+
+ #[test]
+ fn test_link_ref_link() {
+ let inner = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ let ref_link: LinkRef = LinkRef::link(inner.clone());
+ assert!(!ref_link.is_id());
+ assert!(ref_link.is_link());
+ assert_eq!(ref_link.get_id(), 1);
+ assert!(ref_link.as_id().is_none());
+ assert!(ref_link.as_link().is_some());
+ }
+
+ #[test]
+ fn test_link_ref_from_id() {
+ let ref_id: LinkRef = 42u64.into();
+ assert_eq!(ref_id.get_id(), 42);
+ }
+
+ #[test]
+ fn test_link_ref_from_link() {
+ let link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ let ref_link: LinkRef = link.into();
+ assert_eq!(ref_link.get_id(), 1);
+ }
+ }
+
+ mod link_tests {
+ use super::*;
+
+ #[test]
+ fn test_link_new() {
+ let link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ assert_eq!(link.id, 1);
+ assert_eq!(link.source_id(), 2);
+ assert_eq!(link.target_id(), 3);
+ assert!(!link.has_values());
+ }
+
+ #[test]
+ fn test_link_with_values() {
+ let link = Link::with_values(
+ 1u64,
+ LinkRef::Id(2),
+ LinkRef::Id(3),
+ vec![LinkRef::Id(4), LinkRef::Id(5)],
+ );
+ assert!(link.has_values());
+ assert_eq!(link.values.as_ref().unwrap().len(), 2);
+ }
+
+ #[test]
+ fn test_link_point() {
+ let point = Link::::point(42);
+ assert!(point.is_point());
+ assert_eq!(point.id, 42);
+ assert_eq!(point.source_id(), 42);
+ assert_eq!(point.target_id(), 42);
+ }
+
+ #[test]
+ fn test_link_nothing() {
+ let null_link = Link::::nothing();
+ assert!(null_link.is_null());
+ assert_eq!(null_link.id, 0);
+ }
+
+ #[test]
+ fn test_link_nested() {
+ let inner = Link::new(2u64, LinkRef::Id(3), LinkRef::Id(4));
+ let outer = Link::new(1u64, LinkRef::link(inner.clone()), LinkRef::Id(5));
+ assert_eq!(outer.source_id(), 2);
+ assert_eq!(outer.target_id(), 5);
+ }
+ }
+
+ mod pattern_tests {
+ use super::*;
+
+ #[test]
+ fn test_pattern_matches_all() {
+ let pattern = LinkPattern::::all();
+ let link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ assert!(pattern.matches(&link));
+ }
+
+ #[test]
+ fn test_pattern_with_source() {
+ let pattern = LinkPattern::with_source(LinkRef::Id(2u64));
+ let link1 = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ let link2 = Link::new(1u64, LinkRef::Id(5), LinkRef::Id(3));
+ assert!(pattern.matches(&link1));
+ assert!(!pattern.matches(&link2));
+ }
+
+ #[test]
+ fn test_pattern_with_target() {
+ let pattern = LinkPattern::with_target(LinkRef::Id(3u64));
+ let link1 = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ let link2 = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(5));
+ assert!(pattern.matches(&link1));
+ assert!(!pattern.matches(&link2));
+ }
+
+ #[test]
+ fn test_pattern_builder() {
+ let pattern = LinkPattern::::new().source(2u64).target(Any);
+ let link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ assert!(pattern.matches(&link));
+ }
+
+ #[test]
+ fn test_pattern_with_source_target() {
+ let pattern = LinkPattern::with_source_target(LinkRef::Id(2u64), LinkRef::Id(3u64));
+ let link1 = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ let link2 = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(5));
+ assert!(pattern.matches(&link1));
+ assert!(!pattern.matches(&link2));
+ }
+ }
+
+ mod error_tests {
+ use super::*;
+
+ #[test]
+ fn test_error_display() {
+ let err: LinkError = LinkError::NotFound(42);
+ assert!(err.to_string().contains("42"));
+
+ let err2: LinkError = LinkError::AlreadyExists(1);
+ assert!(err2.to_string().contains("already exists"));
+ }
+ }
+}
diff --git a/rust/tests/integration_test.rs b/rust/tests/integration_test.rs
index 54d753c..af2f50b 100644
--- a/rust/tests/integration_test.rs
+++ b/rust/tests/integration_test.rs
@@ -2,8 +2,239 @@
//!
//! These tests verify the public API works correctly.
+#[allow(deprecated)]
use links_queue::{add, delay, multiply};
+use links_queue::{Any, Link, LinkPattern, LinkRef, LinkType, VERSION};
+// =============================================================================
+// Link and LinkRef Integration Tests
+// =============================================================================
+
+mod link_integration_tests {
+ use super::*;
+
+ #[test]
+ fn test_create_simple_link() {
+ let link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ assert_eq!(link.id, 1);
+ assert_eq!(link.source_id(), 2);
+ assert_eq!(link.target_id(), 3);
+ }
+
+ #[test]
+ fn test_create_link_with_different_id_types() {
+ // Test with u32
+ let link_u32 = Link::new(1u32, LinkRef::Id(2u32), LinkRef::Id(3u32));
+ assert_eq!(link_u32.id, 1u32);
+
+ // Test with u64
+ let link_u64 = Link::new(1u64, LinkRef::Id(2u64), LinkRef::Id(3u64));
+ assert_eq!(link_u64.id, 1u64);
+
+ // Test with usize
+ let link_usize = Link::new(1usize, LinkRef::Id(2usize), LinkRef::Id(3usize));
+ assert_eq!(link_usize.id, 1usize);
+ }
+
+ #[test]
+ fn test_nested_link_structure() {
+ // Create inner link
+ let inner = Link::new(10u64, LinkRef::Id(20), LinkRef::Id(30));
+
+ // Create outer link with nested source
+ let outer = Link::new(1u64, LinkRef::link(inner), LinkRef::Id(5));
+
+ assert_eq!(outer.id, 1);
+ assert_eq!(outer.source_id(), 10); // Gets ID of nested link
+ assert_eq!(outer.target_id(), 5);
+
+ // Verify the nested link is accessible
+ if let LinkRef::Link(nested) = &outer.source {
+ assert_eq!(nested.id, 10);
+ assert_eq!(nested.source_id(), 20);
+ assert_eq!(nested.target_id(), 30);
+ } else {
+ panic!("Expected nested link");
+ }
+ }
+
+ #[test]
+ fn test_deeply_nested_links() {
+ // Create a 3-level nested structure
+ let level3 = Link::new(3u64, LinkRef::Id(30), LinkRef::Id(31));
+ let level2 = Link::new(2u64, LinkRef::link(level3), LinkRef::Id(21));
+ let level1 = Link::new(1u64, LinkRef::link(level2), LinkRef::Id(11));
+
+ assert_eq!(level1.source_id(), 2);
+
+ // Navigate to nested links
+ if let LinkRef::Link(l2) = &level1.source {
+ assert_eq!(l2.source_id(), 3);
+ if let LinkRef::Link(l3) = &l2.source {
+ assert_eq!(l3.source_id(), 30);
+ } else {
+ panic!("Expected level 3 nested link");
+ }
+ } else {
+ panic!("Expected level 2 nested link");
+ }
+ }
+
+ #[test]
+ fn test_universal_link_with_values() {
+ let link = Link::with_values(
+ 1u64,
+ LinkRef::Id(2),
+ LinkRef::Id(3),
+ vec![LinkRef::Id(4), LinkRef::Id(5), LinkRef::Id(6)],
+ );
+
+ assert!(link.has_values());
+ let values = link.values.as_ref().unwrap();
+ assert_eq!(values.len(), 3);
+ assert_eq!(values[0].get_id(), 4);
+ assert_eq!(values[1].get_id(), 5);
+ assert_eq!(values[2].get_id(), 6);
+ }
+
+ #[test]
+ fn test_point_link() {
+ let point = Link::::point(42);
+ assert!(point.is_point());
+ assert_eq!(point.id, 42);
+ assert_eq!(point.source_id(), 42);
+ assert_eq!(point.target_id(), 42);
+ }
+
+ #[test]
+ fn test_null_link() {
+ let null_link = Link::::nothing();
+ assert!(null_link.is_null());
+ assert_eq!(null_link.id, 0);
+ assert_eq!(null_link.source_id(), 0);
+ assert_eq!(null_link.target_id(), 0);
+ }
+
+ #[test]
+ fn test_link_ref_conversions() {
+ // From ID
+ let ref_from_id: LinkRef = 42u64.into();
+ assert!(ref_from_id.is_id());
+ assert_eq!(ref_from_id.get_id(), 42);
+
+ // From Link
+ let link = Link::new(1u64, LinkRef::Id(2), LinkRef::Id(3));
+ let ref_from_link: LinkRef = link.into();
+ assert!(ref_from_link.is_link());
+ assert_eq!(ref_from_link.get_id(), 1);
+ }
+}
+
+// =============================================================================
+// Pattern Matching Integration Tests
+// =============================================================================
+
+mod pattern_integration_tests {
+ use super::*;
+
+ #[test]
+ fn test_match_all_pattern() {
+ let pattern = LinkPattern::::all();
+
+ // Should match any link
+ assert!(pattern.matches(&Link::new(1, LinkRef::Id(2), LinkRef::Id(3))));
+ assert!(pattern.matches(&Link::new(100, LinkRef::Id(200), LinkRef::Id(300))));
+ assert!(pattern.matches(&Link::point(42)));
+ assert!(pattern.matches(&Link::nothing()));
+ }
+
+ #[test]
+ fn test_match_by_source() {
+ let pattern = LinkPattern::with_source(LinkRef::Id(5u64));
+
+ assert!(pattern.matches(&Link::new(1, LinkRef::Id(5), LinkRef::Id(10))));
+ assert!(pattern.matches(&Link::new(2, LinkRef::Id(5), LinkRef::Id(20))));
+ assert!(!pattern.matches(&Link::new(1, LinkRef::Id(10), LinkRef::Id(5))));
+ }
+
+ #[test]
+ fn test_match_by_target() {
+ let pattern = LinkPattern::with_target(LinkRef::Id(10u64));
+
+ assert!(pattern.matches(&Link::new(1, LinkRef::Id(5), LinkRef::Id(10))));
+ assert!(pattern.matches(&Link::new(2, LinkRef::Id(20), LinkRef::Id(10))));
+ assert!(!pattern.matches(&Link::new(1, LinkRef::Id(10), LinkRef::Id(5))));
+ }
+
+ #[test]
+ fn test_match_by_source_and_target() {
+ let pattern = LinkPattern::with_source_target(LinkRef::Id(5u64), LinkRef::Id(10u64));
+
+ assert!(pattern.matches(&Link::new(1, LinkRef::Id(5), LinkRef::Id(10))));
+ assert!(!pattern.matches(&Link::new(1, LinkRef::Id(5), LinkRef::Id(20))));
+ assert!(!pattern.matches(&Link::new(1, LinkRef::Id(20), LinkRef::Id(10))));
+ }
+
+ #[test]
+ fn test_builder_pattern() {
+ let pattern = LinkPattern::::new().id(1u64).source(Any).target(10u64);
+
+ assert!(pattern.matches(&Link::new(1, LinkRef::Id(5), LinkRef::Id(10))));
+ assert!(pattern.matches(&Link::new(1, LinkRef::Id(999), LinkRef::Id(10))));
+ assert!(!pattern.matches(&Link::new(2, LinkRef::Id(5), LinkRef::Id(10))));
+ assert!(!pattern.matches(&Link::new(1, LinkRef::Id(5), LinkRef::Id(20))));
+ }
+
+ #[test]
+ fn test_any_wildcard() {
+ let pattern = LinkPattern::::new().source(Any).target(Any);
+
+ // Should match anything since both are Any
+ assert!(pattern.matches(&Link::new(1, LinkRef::Id(100), LinkRef::Id(200))));
+ assert!(pattern.matches(&Link::new(1, LinkRef::Id(0), LinkRef::Id(0))));
+ }
+}
+
+// =============================================================================
+// LinkType Trait Tests
+// =============================================================================
+
+mod link_type_tests {
+ use super::*;
+
+ #[test]
+ fn test_link_type_implementations() {
+ // All numeric types should implement LinkType
+ assert_eq!(u8::zero(), 0u8);
+ assert_eq!(u16::zero(), 0u16);
+ assert_eq!(u32::zero(), 0u32);
+ assert_eq!(u64::zero(), 0u64);
+ assert_eq!(usize::zero(), 0usize);
+
+ assert_eq!(i8::zero(), 0i8);
+ assert_eq!(i16::zero(), 0i16);
+ assert_eq!(i32::zero(), 0i32);
+ assert_eq!(i64::zero(), 0i64);
+ assert_eq!(isize::zero(), 0isize);
+ }
+
+ #[test]
+ fn test_is_nothing() {
+ assert!(0u64.is_nothing());
+ assert!(!1u64.is_nothing());
+ assert!(!42u64.is_nothing());
+
+ assert!(0i64.is_nothing());
+ assert!(!1i64.is_nothing());
+ assert!(!(-1i64).is_nothing());
+ }
+}
+
+// =============================================================================
+// Backward Compatibility Tests
+// =============================================================================
+
+#[allow(deprecated)]
mod add_integration_tests {
use super::*;
@@ -23,6 +254,7 @@ mod add_integration_tests {
}
}
+#[allow(deprecated)]
mod multiply_integration_tests {
use super::*;
@@ -42,6 +274,7 @@ mod multiply_integration_tests {
}
}
+#[allow(deprecated)]
mod delay_integration_tests {
use super::*;
@@ -73,7 +306,7 @@ mod delay_integration_tests {
}
mod version_tests {
- use links_queue::VERSION;
+ use super::*;
#[test]
fn test_version_is_not_empty() {