|
| 1 | +"use strict"; |
| 2 | + |
| 3 | +/** |
| 4 | + * Example usage of the new repository type system |
| 5 | + * Demonstrates Rust-compatible patterns in JavaScript |
| 6 | + */ |
| 7 | + |
| 8 | +const { RepositoryType, Result } = require("./types"); |
| 9 | +const { createRepositoryByType } = require("./factory"); |
| 10 | +const { RepositoryOps } = require("./operations"); |
| 11 | + |
| 12 | +/** |
| 13 | + * Example 1: Creating repositories using factory pattern |
| 14 | + * Factory pattern is common in Rust for complex object construction |
| 15 | + * @returns {Promise<{globus: *, metadata: *}>} object containing created repositories |
| 16 | + * @see https://doc.rust-lang.org/book/ch17-03-oo-design-patterns.html |
| 17 | + */ |
| 18 | +async function createRepositoryExample() { |
| 19 | + // Create a Globus repository |
| 20 | + const globusConfig = { |
| 21 | + id: "science_data_repo", |
| 22 | + type: RepositoryType.GLOBUS, |
| 23 | + title: "Science Data Repository", |
| 24 | + desc: "Repository for scientific datasets", |
| 25 | + capacity: 10000000000, // 10GB |
| 26 | + admins: ["u/scientist1", "u/scientist2"], |
| 27 | + pub_key: "123ABC...", |
| 28 | + address: "data.science.org", |
| 29 | + endpoint: "endpoint-abc123", |
| 30 | + path: "/mnt/storage/repos/science_data_repo", |
| 31 | + domain: "science.org", |
| 32 | + }; |
| 33 | + |
| 34 | + const globusResult = createRepositoryByType(globusConfig); |
| 35 | + if (!globusResult.ok) { |
| 36 | + console.error("Failed to create Globus repo:", globusResult.error); |
| 37 | + return; |
| 38 | + } |
| 39 | + |
| 40 | + // Create a metadata-only repository |
| 41 | + const metadataConfig = { |
| 42 | + id: "metadata_catalog", |
| 43 | + type: RepositoryType.METADATA_ONLY, |
| 44 | + title: "Metadata Catalog", |
| 45 | + desc: "Repository for metadata records only", |
| 46 | + capacity: 1000000, // Logical limit for records |
| 47 | + admins: ["u/cataloger1"], |
| 48 | + }; |
| 49 | + |
| 50 | + const metadataResult = createRepositoryByType(metadataConfig); |
| 51 | + if (!metadataResult.ok) { |
| 52 | + console.error("Failed to create metadata repo:", metadataResult.error); |
| 53 | + return; |
| 54 | + } |
| 55 | + |
| 56 | + return { globus: globusResult.value, metadata: metadataResult.value }; |
| 57 | +} |
| 58 | + |
| 59 | +/** |
| 60 | + * Example 2: Using trait-like operations |
| 61 | + * Using traits allows polymorphic behavior without knowing concrete types |
| 62 | + * @returns {Promise<*>} Allocation result |
| 63 | + * @see https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters |
| 64 | + */ |
| 65 | +async function useRepositoryOperations() { |
| 66 | + // Find a repository |
| 67 | + const findResult = RepositoryOps.find("repo/science_data_repo"); |
| 68 | + if (!findResult.ok) { |
| 69 | + console.error("Repository not found:", findResult.error); |
| 70 | + return; |
| 71 | + } |
| 72 | + |
| 73 | + const repository = findResult.value; |
| 74 | + |
| 75 | + // Check if it supports data operations |
| 76 | + const supportsDataResult = RepositoryOps.supportsDataOperations(repository); |
| 77 | + if (supportsDataResult.ok && supportsDataResult.value) { |
| 78 | + console.log("Repository supports data operations"); |
| 79 | + } |
| 80 | + |
| 81 | + // Create an allocation |
| 82 | + const allocResult = RepositoryOps.createAllocation(repository, { |
| 83 | + subject: "d/dataset_001", |
| 84 | + size: 1000000000, // 1GB |
| 85 | + path: "/datasets/2024/dataset_001", |
| 86 | + metadata: { |
| 87 | + project: "Climate Research", |
| 88 | + created_by: "Dr. Smith", |
| 89 | + }, |
| 90 | + }); |
| 91 | + |
| 92 | + if (!allocResult.ok) { |
| 93 | + console.error("Allocation failed:", allocResult.error); |
| 94 | + return; |
| 95 | + } |
| 96 | + |
| 97 | + // Handle result based on execution method |
| 98 | + const allocation = allocResult.value; |
| 99 | + if (allocation.execution_method === "task") { |
| 100 | + console.log("Allocation queued as task:", allocation.task.task_id); |
| 101 | + // Would monitor task progress... |
| 102 | + } else { |
| 103 | + console.log("Allocation completed directly:", allocation.result); |
| 104 | + } |
| 105 | + |
| 106 | + return allocation; |
| 107 | +} |
| 108 | + |
| 109 | +/** |
| 110 | + * Example 3: Pattern matching on repository types |
| 111 | + * Pattern matching is fundamental in Rust for handling enum variants |
| 112 | + * @param {object} repository - Repository object with type and data fields |
| 113 | + * @returns {{ok: boolean, error?: *, value?: *}} Result of handling repository |
| 114 | + * @see https://doc.rust-lang.org/book/ch06-02-match.html |
| 115 | + */ |
| 116 | +function handleRepositoryByType(repository) { |
| 117 | + // Similar to Rust match expression |
| 118 | + switch (repository.type) { |
| 119 | + case RepositoryType.GLOBUS: |
| 120 | + return handleGlobusRepository(repository.data); |
| 121 | + |
| 122 | + case RepositoryType.METADATA_ONLY: |
| 123 | + return handleMetadataRepository(repository.data); |
| 124 | + |
| 125 | + default: |
| 126 | + return Result.err({ |
| 127 | + code: 400, |
| 128 | + message: `Unknown repository type: ${repository.type}`, |
| 129 | + }); |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +function handleGlobusRepository(repoData) { |
| 134 | + console.log(`Globus repository at ${repoData.endpoint}`); |
| 135 | + // Globus-specific logic... |
| 136 | + return Result.ok({ |
| 137 | + type: "globus", |
| 138 | + endpoint: repoData.endpoint, |
| 139 | + path: repoData.path, |
| 140 | + }); |
| 141 | +} |
| 142 | + |
| 143 | +function handleMetadataRepository(repoData) { |
| 144 | + console.log(`Metadata-only repository: ${repoData.title}`); |
| 145 | + // Metadata-specific logic... |
| 146 | + return Result.ok({ |
| 147 | + type: "metadata", |
| 148 | + record_limit: repoData.capacity, |
| 149 | + }); |
| 150 | +} |
| 151 | + |
| 152 | +/** |
| 153 | + * Example 4: Error handling with Result pattern |
| 154 | + * Early returns emulate Rust's ? operator for error propagation |
| 155 | + * @returns {Promise<{ok: boolean, value?: *, error?: *}>} Result of operations |
| 156 | + * @see https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator |
| 157 | + */ |
| 158 | +async function robustRepositoryOperation() { |
| 159 | + // Chain operations with early return on error |
| 160 | + const findResult = RepositoryOps.find("repo/test_repo"); |
| 161 | + if (!findResult.ok) { |
| 162 | + return findResult; // Propagate error - like ? in Rust |
| 163 | + } |
| 164 | + |
| 165 | + const repository = findResult.value; |
| 166 | + |
| 167 | + const validateResult = RepositoryOps.validate(repository); |
| 168 | + if (!validateResult.ok) { |
| 169 | + return validateResult; // Propagate error |
| 170 | + } |
| 171 | + |
| 172 | + const capacityResult = RepositoryOps.getCapacityInfo(repository); |
| 173 | + if (!capacityResult.ok) { |
| 174 | + return capacityResult; // Propagate error |
| 175 | + } |
| 176 | + |
| 177 | + // All operations succeeded |
| 178 | + return Result.ok({ |
| 179 | + repository: repository, |
| 180 | + capacity: capacityResult.value, |
| 181 | + }); |
| 182 | +} |
| 183 | + |
| 184 | +/** |
| 185 | + * Example 5: Composition over inheritance |
| 186 | + * Rust doesn't have inheritance - prefer composition of behaviors |
| 187 | + * @type {object} |
| 188 | + * @property {function(object, string): void} logAccess - Log repository access |
| 189 | + * @property {function(object, number): {ok: boolean, error?: *, value?: *}} checkQuota - Check repository quota |
| 190 | + * @property {function(object, object): Promise<{ok: boolean, error?: *, value?: *}>} allocateWithQuotaCheck - Allocate with quota check |
| 191 | + * @see https://doc.rust-lang.org/book/ch17-03-oo-design-patterns.html |
| 192 | + */ |
| 193 | +const RepositoryBehaviors = { |
| 194 | + // Shared behaviors as standalone functions |
| 195 | + logAccess: (repository, userId) => { |
| 196 | + console.log(`User ${userId} accessed repository ${repository.data._id}`); |
| 197 | + }, |
| 198 | + |
| 199 | + checkQuota: (repository, requestedSize) => { |
| 200 | + const capacityResult = RepositoryOps.getCapacityInfo(repository); |
| 201 | + if (!capacityResult.ok) return capacityResult; |
| 202 | + |
| 203 | + const capacity = capacityResult.value; |
| 204 | + if (capacity.available_capacity < requestedSize) { |
| 205 | + return Result.err({ |
| 206 | + code: 507, |
| 207 | + message: "Insufficient storage capacity", |
| 208 | + }); |
| 209 | + } |
| 210 | + return Result.ok(true); |
| 211 | + }, |
| 212 | + |
| 213 | + // Type-specific behaviors composed from shared ones |
| 214 | + allocateWithQuotaCheck: async (repository, params) => { |
| 215 | + // Compose behaviors |
| 216 | + const quotaResult = RepositoryBehaviors.checkQuota(repository, params.size); |
| 217 | + if (!quotaResult.ok) return quotaResult; |
| 218 | + |
| 219 | + RepositoryBehaviors.logAccess(repository, params.requested_by); |
| 220 | + |
| 221 | + return RepositoryOps.createAllocation(repository, params); |
| 222 | + }, |
| 223 | +}; |
| 224 | + |
| 225 | +module.exports = { |
| 226 | + createRepositoryExample, |
| 227 | + useRepositoryOperations, |
| 228 | + handleRepositoryByType, |
| 229 | + robustRepositoryOperation, |
| 230 | + RepositoryBehaviors, |
| 231 | +}; |
0 commit comments