@powerhousedao/reactor-attachments 6.2.3-dev.5 → 6.2.3-dev.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attachment-schema-compiler-rIaJpiHO.js +262 -0
- package/dist/attachment-schema-compiler-rIaJpiHO.js.map +1 -0
- package/dist/client.d.ts +52 -36
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +23 -6
- package/dist/client.js.map +1 -1
- package/dist/hash-CKHeI4AH.js +26 -0
- package/dist/hash-CKHeI4AH.js.map +1 -0
- package/dist/index.d.ts +80 -110
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +131 -403
- package/dist/index.js.map +1 -1
- package/dist/{null-attachment-transport-CMrO_ZKA.d.ts → interfaces-jHW9JXv1.d.ts} +30 -326
- package/dist/interfaces-jHW9JXv1.d.ts.map +1 -0
- package/dist/{null-attachment-transport-CrsUafCi.js → null-attachment-transport-BGG9cRC-.js} +24 -283
- package/dist/null-attachment-transport-BGG9cRC-.js.map +1 -0
- package/dist/null-attachment-transport-dg7vi6F4.d.ts +327 -0
- package/dist/null-attachment-transport-dg7vi6F4.d.ts.map +1 -0
- package/dist/replication.d.ts +955 -0
- package/dist/replication.d.ts.map +1 -0
- package/dist/replication.js +1975 -0
- package/dist/replication.js.map +1 -0
- package/dist/switchboard-attachment-transport-BH3ZAmxE.js +284 -0
- package/dist/switchboard-attachment-transport-BH3ZAmxE.js.map +1 -0
- package/dist/types-AagIcjTD.d.ts +61 -0
- package/dist/types-AagIcjTD.d.ts.map +1 -0
- package/package.json +15 -7
- package/dist/null-attachment-transport-CMrO_ZKA.d.ts.map +0 -1
- package/dist/null-attachment-transport-CrsUafCi.js.map +0 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { a as parseRef } from "./switchboard-attachment-transport-BH3ZAmxE.js";
|
|
2
|
+
import { generatorTypeDefs } from "@powerhousedao/document-engineering/graphql";
|
|
3
|
+
import { constantCase, pascalCase } from "change-case";
|
|
4
|
+
import { Kind, buildASTSchema, getNamedType, isInputObjectType, isListType, isNonNullType, parse, print } from "graphql";
|
|
5
|
+
//#region src/reference-index/attachment-schema-compiler.ts
|
|
6
|
+
const ATTACHMENT_REF_TYPE = "AttachmentRef";
|
|
7
|
+
const CODEGEN_SCALAR_NAMES = /* @__PURE__ */ new Set([
|
|
8
|
+
"Unknown",
|
|
9
|
+
"DateTime",
|
|
10
|
+
"Address",
|
|
11
|
+
ATTACHMENT_REF_TYPE,
|
|
12
|
+
...Object.keys(generatorTypeDefs)
|
|
13
|
+
]);
|
|
14
|
+
function describeContext(context) {
|
|
15
|
+
return `document type "${context.documentType}", version ${context.version}, action "${context.actionType}"`;
|
|
16
|
+
}
|
|
17
|
+
function compilationError(context, reason) {
|
|
18
|
+
return /* @__PURE__ */ new Error(`Attachment schema compilation failed for ${describeContext(context)}: ${reason}`);
|
|
19
|
+
}
|
|
20
|
+
function extractionError(context, path, reason) {
|
|
21
|
+
return /* @__PURE__ */ new Error(`Attachment extraction failed for ${describeContext(context)} at ${path}: ${reason}`);
|
|
22
|
+
}
|
|
23
|
+
function applicableSpecification(module, context) {
|
|
24
|
+
const matches = module.documentModel.global.specifications.filter((specification) => specification.version === context.version);
|
|
25
|
+
if (matches.length !== 1) throw compilationError(context, matches.length === 0 ? "the module has no matching specification" : "the module has multiple matching specifications");
|
|
26
|
+
return matches[0];
|
|
27
|
+
}
|
|
28
|
+
function parseOperations(specification, context) {
|
|
29
|
+
return specification.modules.flatMap((moduleSpecification) => moduleSpecification.operations.map((operation) => {
|
|
30
|
+
if (operation.schema === null) return {
|
|
31
|
+
document: null,
|
|
32
|
+
operation
|
|
33
|
+
};
|
|
34
|
+
try {
|
|
35
|
+
return {
|
|
36
|
+
document: parse(operation.schema),
|
|
37
|
+
operation
|
|
38
|
+
};
|
|
39
|
+
} catch {
|
|
40
|
+
throw compilationError(context, "an operation has invalid GraphQL SDL");
|
|
41
|
+
}
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
function selectOperation(operations, context) {
|
|
45
|
+
const matches = operations.filter(({ operation }) => operation.name !== null && constantCase(operation.name) === context.actionType);
|
|
46
|
+
if (matches.length > 1) throw compilationError(context, "multiple operations map to the action");
|
|
47
|
+
if (matches.length === 0) return null;
|
|
48
|
+
return matches[0];
|
|
49
|
+
}
|
|
50
|
+
function buildEffectiveSchema(specification, context) {
|
|
51
|
+
const scalarSchemas = Array.from(CODEGEN_SCALAR_NAMES, (name) => `scalar ${name}`);
|
|
52
|
+
const stateSchemas = Object.values(specification.state).map((state) => state.schema);
|
|
53
|
+
const operationSchemas = specification.modules.flatMap((moduleSpecification) => moduleSpecification.operations.flatMap((operation) => operation.schema === null ? [] : [operation.schema]));
|
|
54
|
+
try {
|
|
55
|
+
const document = parse([
|
|
56
|
+
...scalarSchemas,
|
|
57
|
+
...stateSchemas,
|
|
58
|
+
...operationSchemas
|
|
59
|
+
].filter(Boolean).join("\n\n"));
|
|
60
|
+
return buildASTSchema(dedupeTypeDefinitions(document));
|
|
61
|
+
} catch {
|
|
62
|
+
throw compilationError(context, "the effective GraphQL schema is invalid");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function dedupeTypeDefinitions(document) {
|
|
66
|
+
const seen = /* @__PURE__ */ new Map();
|
|
67
|
+
const definitions = document.definitions.filter((definition) => {
|
|
68
|
+
if (!("name" in definition) || definition.name?.value === void 0 || definition.kind === Kind.SCALAR_TYPE_DEFINITION) return true;
|
|
69
|
+
const name = definition.name.value;
|
|
70
|
+
const printed = print(definition);
|
|
71
|
+
const existing = seen.get(name);
|
|
72
|
+
if (existing === void 0) {
|
|
73
|
+
seen.set(name, printed);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return existing !== printed;
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
...document,
|
|
80
|
+
definitions
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function attachmentReachableTypes(definitions) {
|
|
84
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
85
|
+
let changed = true;
|
|
86
|
+
while (changed) {
|
|
87
|
+
changed = false;
|
|
88
|
+
for (const [name, definition] of definitions) {
|
|
89
|
+
if (reachable.has(name)) continue;
|
|
90
|
+
if (Object.values(definition.getFields()).some((field) => {
|
|
91
|
+
const typeName = getNamedType(field.type).name;
|
|
92
|
+
return typeName === ATTACHMENT_REF_TYPE || definitions.has(typeName) && reachable.has(typeName);
|
|
93
|
+
})) {
|
|
94
|
+
reachable.add(name);
|
|
95
|
+
changed = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return reachable;
|
|
100
|
+
}
|
|
101
|
+
function compileValuePlan(type, objectPlans) {
|
|
102
|
+
if (isNonNullType(type)) return {
|
|
103
|
+
...compileValuePlan(type.ofType, objectPlans),
|
|
104
|
+
required: true
|
|
105
|
+
};
|
|
106
|
+
if (isListType(type)) return {
|
|
107
|
+
item: compileValuePlan(type.ofType, objectPlans),
|
|
108
|
+
kind: "list",
|
|
109
|
+
required: false
|
|
110
|
+
};
|
|
111
|
+
const typeName = type.name;
|
|
112
|
+
if (typeName === ATTACHMENT_REF_TYPE) return {
|
|
113
|
+
kind: "attachment",
|
|
114
|
+
required: false
|
|
115
|
+
};
|
|
116
|
+
const body = objectPlans.get(typeName);
|
|
117
|
+
if (!body) throw new Error(`Internal attachment schema plan error for ${typeName}`);
|
|
118
|
+
return {
|
|
119
|
+
body,
|
|
120
|
+
kind: "object",
|
|
121
|
+
required: false
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function compileRootPlan(rootName, definitions) {
|
|
125
|
+
const reachable = attachmentReachableTypes(definitions);
|
|
126
|
+
if (!reachable.has(rootName)) return null;
|
|
127
|
+
const objectPlans = /* @__PURE__ */ new Map();
|
|
128
|
+
for (const name of reachable) objectPlans.set(name, { fields: [] });
|
|
129
|
+
for (const name of reachable) {
|
|
130
|
+
const definition = definitions.get(name);
|
|
131
|
+
const body = objectPlans.get(name);
|
|
132
|
+
if (!definition || !body) continue;
|
|
133
|
+
for (const field of Object.values(definition.getFields())) {
|
|
134
|
+
const typeName = getNamedType(field.type).name;
|
|
135
|
+
if (typeName !== ATTACHMENT_REF_TYPE && !reachable.has(typeName)) continue;
|
|
136
|
+
body.fields.push({
|
|
137
|
+
hasDefault: field.defaultValue !== void 0,
|
|
138
|
+
name: field.name,
|
|
139
|
+
value: compileValuePlan(field.type, objectPlans)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return objectPlans.get(rootName) ?? null;
|
|
144
|
+
}
|
|
145
|
+
function readOwnField(value, fieldName, context, path) {
|
|
146
|
+
try {
|
|
147
|
+
if (!Object.prototype.hasOwnProperty.call(value, fieldName)) return {
|
|
148
|
+
present: false,
|
|
149
|
+
value: void 0
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
present: true,
|
|
153
|
+
value: value[fieldName]
|
|
154
|
+
};
|
|
155
|
+
} catch {
|
|
156
|
+
throw extractionError(context, path, "the declared field cannot be read");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function extractValue(plan, value, path, context, refs, seenRefs, activeObjects) {
|
|
160
|
+
if (value === null || value === void 0) {
|
|
161
|
+
if (plan.required) throw extractionError(context, path, "a required value is missing or null");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (plan.kind === "attachment") {
|
|
165
|
+
if (typeof value !== "string") throw extractionError(context, path, "expected an AttachmentRef string");
|
|
166
|
+
try {
|
|
167
|
+
parseRef(value);
|
|
168
|
+
} catch {
|
|
169
|
+
throw extractionError(context, path, "the AttachmentRef is malformed");
|
|
170
|
+
}
|
|
171
|
+
if (!seenRefs.has(value)) {
|
|
172
|
+
seenRefs.add(value);
|
|
173
|
+
refs.push(value);
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (plan.kind === "list") {
|
|
178
|
+
if (!Array.isArray(value)) throw extractionError(context, path, "expected a list");
|
|
179
|
+
for (let index = 0; index < value.length; index += 1) extractValue(plan.item, value[index], `${path}[${index}]`, context, refs, seenRefs, activeObjects);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (typeof value !== "object" || Array.isArray(value)) throw extractionError(context, path, "expected an input object");
|
|
183
|
+
if (activeObjects.has(value)) throw extractionError(context, path, "the input value contains a cycle");
|
|
184
|
+
activeObjects.add(value);
|
|
185
|
+
try {
|
|
186
|
+
const record = value;
|
|
187
|
+
for (const field of plan.body.fields) {
|
|
188
|
+
const fieldPath = `${path}.${field.name}`;
|
|
189
|
+
const result = readOwnField(record, field.name, context, fieldPath);
|
|
190
|
+
if ((!result.present || result.value === void 0) && field.hasDefault) continue;
|
|
191
|
+
extractValue(field.value, result.value, fieldPath, context, refs, seenRefs, activeObjects);
|
|
192
|
+
}
|
|
193
|
+
} finally {
|
|
194
|
+
activeObjects.delete(value);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
var SchemaCompiledAttachmentExtractor = class {
|
|
198
|
+
context;
|
|
199
|
+
rootPlan;
|
|
200
|
+
constructor(context, rootPlan) {
|
|
201
|
+
this.context = context;
|
|
202
|
+
this.rootPlan = rootPlan;
|
|
203
|
+
}
|
|
204
|
+
extract(action) {
|
|
205
|
+
if (action.type !== this.context.actionType) throw extractionError(this.context, "input", "the action type does not match the compiled schema");
|
|
206
|
+
if (!this.rootPlan) return [];
|
|
207
|
+
if (action.input === null || typeof action.input !== "object" || Array.isArray(action.input)) throw extractionError(this.context, "input", "expected an input object");
|
|
208
|
+
const refs = [];
|
|
209
|
+
const seenRefs = /* @__PURE__ */ new Set();
|
|
210
|
+
const activeObjects = /* @__PURE__ */ new WeakSet();
|
|
211
|
+
activeObjects.add(action.input);
|
|
212
|
+
try {
|
|
213
|
+
const input = action.input;
|
|
214
|
+
for (const field of this.rootPlan.fields) {
|
|
215
|
+
const path = `input.${field.name}`;
|
|
216
|
+
const result = readOwnField(input, field.name, this.context, path);
|
|
217
|
+
if ((!result.present || result.value === void 0) && field.hasDefault) continue;
|
|
218
|
+
extractValue(field.value, result.value, path, this.context, refs, seenRefs, activeObjects);
|
|
219
|
+
}
|
|
220
|
+
} finally {
|
|
221
|
+
activeObjects.delete(action.input);
|
|
222
|
+
}
|
|
223
|
+
return refs;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
function compileExtractor(module, actionType) {
|
|
227
|
+
const context = {
|
|
228
|
+
actionType,
|
|
229
|
+
documentType: module.documentModel.global.id,
|
|
230
|
+
version: module.version ?? 1
|
|
231
|
+
};
|
|
232
|
+
const specification = applicableSpecification(module, context);
|
|
233
|
+
const selected = selectOperation(parseOperations(specification, context), context);
|
|
234
|
+
if (selected === null || selected.operation.schema === null) return new SchemaCompiledAttachmentExtractor(context, null);
|
|
235
|
+
const effectiveSchema = buildEffectiveSchema(specification, context);
|
|
236
|
+
const operationName = selected.operation.name;
|
|
237
|
+
if (operationName === null) throw compilationError(context, "the operation has no name");
|
|
238
|
+
const rootName = `${pascalCase(operationName)}Input`;
|
|
239
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
240
|
+
for (const type of Object.values(effectiveSchema.getTypeMap())) if (isInputObjectType(type) && !type.name.startsWith("__")) definitions.set(type.name, type);
|
|
241
|
+
if (!(selected.document?.definitions.filter((definition) => (definition.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION || definition.kind === Kind.INPUT_OBJECT_TYPE_EXTENSION) && definition.name.value === rootName))?.length || !definitions.has(rootName)) throw compilationError(context, `the operation does not declare its expected root input "${rootName}"`);
|
|
242
|
+
return new SchemaCompiledAttachmentExtractor(context, compileRootPlan(rootName, definitions));
|
|
243
|
+
}
|
|
244
|
+
var AttachmentSchemaCompiler = class {
|
|
245
|
+
cache = /* @__PURE__ */ new WeakMap();
|
|
246
|
+
forModuleAction(module, actionType) {
|
|
247
|
+
let moduleCache = this.cache.get(module);
|
|
248
|
+
if (!moduleCache) {
|
|
249
|
+
moduleCache = /* @__PURE__ */ new Map();
|
|
250
|
+
this.cache.set(module, moduleCache);
|
|
251
|
+
}
|
|
252
|
+
const cached = moduleCache.get(actionType);
|
|
253
|
+
if (cached) return cached;
|
|
254
|
+
const compiled = compileExtractor(module, actionType);
|
|
255
|
+
moduleCache.set(actionType, compiled);
|
|
256
|
+
return compiled;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
//#endregion
|
|
260
|
+
export { AttachmentSchemaCompiler as t };
|
|
261
|
+
|
|
262
|
+
//# sourceMappingURL=attachment-schema-compiler-rIaJpiHO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-schema-compiler-rIaJpiHO.js","names":[],"sources":["../src/reference-index/attachment-schema-compiler.ts"],"sourcesContent":["import type { AttachmentRef } from \"@powerhousedao/reactor\";\nimport { generatorTypeDefs } from \"@powerhousedao/document-engineering/graphql\";\nimport type {\n Action,\n DocumentModelModule,\n DocumentSpecification,\n OperationSpecification,\n} from \"@powerhousedao/shared/document-model\";\nimport { constantCase, pascalCase } from \"change-case\";\nimport {\n buildASTSchema,\n getNamedType,\n isInputObjectType,\n isListType,\n isNonNullType,\n Kind,\n parse,\n print,\n type DocumentNode,\n type GraphQLInputObjectType,\n type GraphQLInputType,\n type GraphQLSchema,\n} from \"graphql\";\nimport { parseRef } from \"../ref.js\";\nimport type {\n CompiledAttachmentExtractor,\n IAttachmentSchemaCompiler,\n} from \"./types.js\";\n\nconst ATTACHMENT_REF_TYPE = \"AttachmentRef\";\nconst CODEGEN_SCALAR_NAMES = new Set([\n \"Unknown\",\n \"DateTime\",\n \"Address\",\n ATTACHMENT_REF_TYPE,\n ...Object.keys(generatorTypeDefs as Record<string, string>),\n]);\n\ntype CompilerContext = {\n actionType: string;\n documentType: string;\n version: number;\n};\n\ntype ParsedOperation = {\n document: DocumentNode | null;\n operation: OperationSpecification;\n};\n\ntype ObjectPlan = {\n fields: FieldPlan[];\n};\n\ntype FieldPlan = {\n hasDefault: boolean;\n name: string;\n value: ValuePlan;\n};\n\ntype ReadFieldResult =\n | { present: false; value: undefined }\n | { present: true; value: unknown };\n\ntype ValuePlan =\n | {\n kind: \"attachment\";\n required: boolean;\n }\n | {\n item: ValuePlan;\n kind: \"list\";\n required: boolean;\n }\n | {\n body: ObjectPlan;\n kind: \"object\";\n required: boolean;\n };\n\nfunction describeContext(context: CompilerContext): string {\n return `document type \"${context.documentType}\", version ${context.version}, action \"${context.actionType}\"`;\n}\n\nfunction compilationError(context: CompilerContext, reason: string): Error {\n return new Error(\n `Attachment schema compilation failed for ${describeContext(context)}: ${reason}`,\n );\n}\n\nfunction extractionError(\n context: CompilerContext,\n path: string,\n reason: string,\n): Error {\n return new Error(\n `Attachment extraction failed for ${describeContext(context)} at ${path}: ${reason}`,\n );\n}\n\nfunction applicableSpecification(\n module: DocumentModelModule,\n context: CompilerContext,\n): DocumentSpecification {\n const matches = module.documentModel.global.specifications.filter(\n (specification) => specification.version === context.version,\n );\n if (matches.length !== 1) {\n throw compilationError(\n context,\n matches.length === 0\n ? \"the module has no matching specification\"\n : \"the module has multiple matching specifications\",\n );\n }\n return matches[0];\n}\n\nfunction parseOperations(\n specification: DocumentSpecification,\n context: CompilerContext,\n): ParsedOperation[] {\n return specification.modules.flatMap((moduleSpecification) =>\n moduleSpecification.operations.map((operation) => {\n if (operation.schema === null) return { document: null, operation };\n try {\n return { document: parse(operation.schema), operation };\n } catch {\n throw compilationError(context, \"an operation has invalid GraphQL SDL\");\n }\n }),\n );\n}\n\nfunction selectOperation(\n operations: ParsedOperation[],\n context: CompilerContext,\n): ParsedOperation | null {\n const matches = operations.filter(\n ({ operation }) =>\n operation.name !== null &&\n constantCase(operation.name) === context.actionType,\n );\n if (matches.length > 1) {\n throw compilationError(context, \"multiple operations map to the action\");\n }\n // Base/system actions (document creation, renames, undo) are not part of a\n // model's specification, so they cannot declare AttachmentRef fields.\n // They compile to the no-reference fast path instead of failing the stream.\n if (matches.length === 0) return null;\n return matches[0];\n}\n\nfunction buildEffectiveSchema(\n specification: DocumentSpecification,\n context: CompilerContext,\n): GraphQLSchema {\n const scalarSchemas = Array.from(\n CODEGEN_SCALAR_NAMES,\n (name) => `scalar ${name}`,\n );\n const stateSchemas = Object.values(specification.state).map(\n (state) => state.schema,\n );\n const operationSchemas = specification.modules.flatMap(\n (moduleSpecification) =>\n moduleSpecification.operations.flatMap((operation) =>\n operation.schema === null ? [] : [operation.schema],\n ),\n );\n\n try {\n const document = parse(\n [...scalarSchemas, ...stateSchemas, ...operationSchemas]\n .filter(Boolean)\n .join(\"\\n\\n\"),\n );\n return buildASTSchema(dedupeTypeDefinitions(document));\n } catch {\n throw compilationError(context, \"the effective GraphQL schema is invalid\");\n }\n}\n\n// Collapse identical repeats of a type (state schema vs operation schemas).\n// Conflicting or scalar duplicates stay in so the build still rejects them.\nfunction dedupeTypeDefinitions(document: DocumentNode): DocumentNode {\n const seen = new Map<string, string>();\n const definitions = document.definitions.filter((definition) => {\n if (\n !(\"name\" in definition) ||\n definition.name?.value === undefined ||\n definition.kind === Kind.SCALAR_TYPE_DEFINITION\n ) {\n return true;\n }\n const name = definition.name.value;\n const printed = print(definition);\n const existing = seen.get(name);\n if (existing === undefined) {\n seen.set(name, printed);\n return true;\n }\n return existing !== printed;\n });\n return { ...document, definitions };\n}\n\nfunction attachmentReachableTypes(\n definitions: Map<string, GraphQLInputObjectType>,\n): Set<string> {\n const reachable = new Set<string>();\n let changed = true;\n\n while (changed) {\n changed = false;\n for (const [name, definition] of definitions) {\n if (reachable.has(name)) continue;\n const reachesAttachment = Object.values(definition.getFields()).some(\n (field) => {\n const typeName = getNamedType(field.type).name;\n return (\n typeName === ATTACHMENT_REF_TYPE ||\n (definitions.has(typeName) && reachable.has(typeName))\n );\n },\n );\n if (reachesAttachment) {\n reachable.add(name);\n changed = true;\n }\n }\n }\n\n return reachable;\n}\n\nfunction compileValuePlan(\n type: GraphQLInputType,\n objectPlans: Map<string, ObjectPlan>,\n): ValuePlan {\n if (isNonNullType(type)) {\n return { ...compileValuePlan(type.ofType, objectPlans), required: true };\n }\n if (isListType(type)) {\n return {\n item: compileValuePlan(type.ofType, objectPlans),\n kind: \"list\",\n required: false,\n };\n }\n\n const typeName = type.name;\n if (typeName === ATTACHMENT_REF_TYPE) {\n return { kind: \"attachment\", required: false };\n }\n const body = objectPlans.get(typeName);\n if (!body) {\n throw new Error(`Internal attachment schema plan error for ${typeName}`);\n }\n return { body, kind: \"object\", required: false };\n}\n\nfunction compileRootPlan(\n rootName: string,\n definitions: Map<string, GraphQLInputObjectType>,\n): ObjectPlan | null {\n const reachable = attachmentReachableTypes(definitions);\n if (!reachable.has(rootName)) return null;\n\n const objectPlans = new Map<string, ObjectPlan>();\n for (const name of reachable) objectPlans.set(name, { fields: [] });\n\n for (const name of reachable) {\n const definition = definitions.get(name);\n const body = objectPlans.get(name);\n if (!definition || !body) continue;\n for (const field of Object.values(definition.getFields())) {\n const typeName = getNamedType(field.type).name;\n if (typeName !== ATTACHMENT_REF_TYPE && !reachable.has(typeName)) {\n continue;\n }\n body.fields.push({\n hasDefault: field.defaultValue !== undefined,\n name: field.name,\n value: compileValuePlan(field.type, objectPlans),\n });\n }\n }\n\n return objectPlans.get(rootName) ?? null;\n}\n\nfunction readOwnField(\n value: Record<string, unknown>,\n fieldName: string,\n context: CompilerContext,\n path: string,\n): ReadFieldResult {\n try {\n if (!Object.prototype.hasOwnProperty.call(value, fieldName)) {\n return { present: false, value: undefined };\n }\n return { present: true, value: value[fieldName] };\n } catch {\n throw extractionError(context, path, \"the declared field cannot be read\");\n }\n}\n\nfunction extractValue(\n plan: ValuePlan,\n value: unknown,\n path: string,\n context: CompilerContext,\n refs: AttachmentRef[],\n seenRefs: Set<string>,\n activeObjects: WeakSet<object>,\n): void {\n if (value === null || value === undefined) {\n if (plan.required) {\n throw extractionError(\n context,\n path,\n \"a required value is missing or null\",\n );\n }\n return;\n }\n\n if (plan.kind === \"attachment\") {\n if (typeof value !== \"string\") {\n throw extractionError(context, path, \"expected an AttachmentRef string\");\n }\n try {\n parseRef(value as AttachmentRef);\n } catch {\n throw extractionError(context, path, \"the AttachmentRef is malformed\");\n }\n if (!seenRefs.has(value)) {\n seenRefs.add(value);\n refs.push(value as AttachmentRef);\n }\n return;\n }\n\n if (plan.kind === \"list\") {\n if (!Array.isArray(value)) {\n throw extractionError(context, path, \"expected a list\");\n }\n for (let index = 0; index < value.length; index += 1) {\n extractValue(\n plan.item,\n value[index],\n `${path}[${index}]`,\n context,\n refs,\n seenRefs,\n activeObjects,\n );\n }\n return;\n }\n\n if (typeof value !== \"object\" || Array.isArray(value)) {\n throw extractionError(context, path, \"expected an input object\");\n }\n if (activeObjects.has(value)) {\n throw extractionError(context, path, \"the input value contains a cycle\");\n }\n\n activeObjects.add(value);\n try {\n const record = value as Record<string, unknown>;\n for (const field of plan.body.fields) {\n const fieldPath = `${path}.${field.name}`;\n const result = readOwnField(record, field.name, context, fieldPath);\n if ((!result.present || result.value === undefined) && field.hasDefault) {\n continue;\n }\n extractValue(\n field.value,\n result.value,\n fieldPath,\n context,\n refs,\n seenRefs,\n activeObjects,\n );\n }\n } finally {\n activeObjects.delete(value);\n }\n}\n\nclass SchemaCompiledAttachmentExtractor implements CompiledAttachmentExtractor {\n constructor(\n private readonly context: CompilerContext,\n private readonly rootPlan: ObjectPlan | null,\n ) {}\n\n extract(action: Action): AttachmentRef[] {\n if (action.type !== this.context.actionType) {\n throw extractionError(\n this.context,\n \"input\",\n \"the action type does not match the compiled schema\",\n );\n }\n if (!this.rootPlan) return [];\n if (\n action.input === null ||\n typeof action.input !== \"object\" ||\n Array.isArray(action.input)\n ) {\n throw extractionError(this.context, \"input\", \"expected an input object\");\n }\n\n const refs: AttachmentRef[] = [];\n const seenRefs = new Set<string>();\n const activeObjects = new WeakSet<object>();\n activeObjects.add(action.input);\n try {\n const input = action.input as Record<string, unknown>;\n for (const field of this.rootPlan.fields) {\n const path = `input.${field.name}`;\n const result = readOwnField(input, field.name, this.context, path);\n if (\n (!result.present || result.value === undefined) &&\n field.hasDefault\n ) {\n continue;\n }\n extractValue(\n field.value,\n result.value,\n path,\n this.context,\n refs,\n seenRefs,\n activeObjects,\n );\n }\n } finally {\n activeObjects.delete(action.input);\n }\n return refs;\n }\n}\n\nfunction compileExtractor(\n module: DocumentModelModule,\n actionType: string,\n): CompiledAttachmentExtractor {\n const context: CompilerContext = {\n actionType,\n documentType: module.documentModel.global.id,\n version: module.version ?? 1,\n };\n const specification = applicableSpecification(module, context);\n const operations = parseOperations(specification, context);\n const selected = selectOperation(operations, context);\n if (selected === null || selected.operation.schema === null) {\n return new SchemaCompiledAttachmentExtractor(context, null);\n }\n const effectiveSchema = buildEffectiveSchema(specification, context);\n\n const operationName = selected.operation.name;\n if (operationName === null) {\n throw compilationError(context, \"the operation has no name\");\n }\n const rootName = `${pascalCase(operationName)}Input`;\n const definitions = new Map<string, GraphQLInputObjectType>();\n for (const type of Object.values(effectiveSchema.getTypeMap())) {\n if (isInputObjectType(type) && !type.name.startsWith(\"__\")) {\n definitions.set(type.name, type);\n }\n }\n const rootDefinitions = selected.document?.definitions.filter(\n (definition) =>\n (definition.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION ||\n definition.kind === Kind.INPUT_OBJECT_TYPE_EXTENSION) &&\n definition.name.value === rootName,\n );\n if (!rootDefinitions?.length || !definitions.has(rootName)) {\n throw compilationError(\n context,\n `the operation does not declare its expected root input \"${rootName}\"`,\n );\n }\n\n return new SchemaCompiledAttachmentExtractor(\n context,\n compileRootPlan(rootName, definitions),\n );\n}\n\nexport class AttachmentSchemaCompiler implements IAttachmentSchemaCompiler {\n private readonly cache = new WeakMap<\n DocumentModelModule,\n Map<string, CompiledAttachmentExtractor>\n >();\n\n forModuleAction(\n module: DocumentModelModule,\n actionType: string,\n ): CompiledAttachmentExtractor {\n let moduleCache = this.cache.get(module);\n if (!moduleCache) {\n moduleCache = new Map();\n this.cache.set(module, moduleCache);\n }\n\n const cached = moduleCache.get(actionType);\n if (cached) return cached;\n\n const compiled = compileExtractor(module, actionType);\n moduleCache.set(actionType, compiled);\n return compiled;\n }\n}\n"],"mappings":"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"}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as InvalidAttachmentRef, D as AttachmentTransferStage, E as AttachmentTransferError, M as SizeMismatch, N as UploadTooLarge, O as HashMismatch, S as AttachmentAlreadyExists, T as AttachmentPending, _ as createRef, a as RemoteAttachmentUpload, b as parseAttachmentDownloadTarget, c as XhrUploadTransportOptions, d as AttachmentUploadResponse, f as AttachmentUploadTransport, g as ParsedRef, h as SwitchboardTransportConfig, i as RemoteAttachmentUploadFactory, j as ReservationNotFound, l as createXhrUploadTransport, m as SwitchboardAttachmentTransport, n as createRemoteAttachmentService, o as RemoteReservationStore, p as createFetchUploadTransport, r as RemoteAttachmentStore, s as SwitchboardClientConfig, t as NullAttachmentTransport, u as AttachmentUploadRequest, v as parseRef, w as AttachmentNotFound, x as parseAttachmentUploadTarget, y as AttachmentService } from "./null-attachment-transport-dg7vi6F4.js";
|
|
2
|
+
import { A as UploadFirstReserveAttachmentOptions, C as AttachmentUploadResult, D as ReserveAttachmentOptions, E as Reservation, O as TransportFetchResult, S as AttachmentTransportConfig, T as HashFirstReserveAttachmentOptions, _ as AttachmentResponse, a as IAttachmentTransport, b as AttachmentStatus, c as IAttachmentUploadFactory, f as AttachmentDownloadOptions, g as AttachmentMetadata, h as AttachmentHeader, i as IAttachmentStore, k as TransportResponse, l as IReservationStore, m as AttachmentDownloadTargetOptions, n as IAttachmentReader, o as IAttachmentTransportFactory, p as AttachmentDownloadTarget, r as IAttachmentService, s as IAttachmentUpload, v as AttachmentSendOptions, w as AttachmentUploadTarget, x as AttachmentTargetHeaders, y as AttachmentStatOptions } from "./interfaces-jHW9JXv1.js";
|
|
2
3
|
import { AttachmentHash, AttachmentRef } from "@powerhousedao/reactor";
|
|
3
|
-
|
|
4
4
|
//#region src/concurrency.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Ordered result of one batch item. `index` always mirrors the input
|
|
@@ -17,7 +17,8 @@ type BatchItemResult<R> = {
|
|
|
17
17
|
error: unknown;
|
|
18
18
|
};
|
|
19
19
|
type RunWithConcurrencyOptions<R = unknown> = {
|
|
20
|
-
/** Maximum simultaneously running workers. Must be a positive integer. */
|
|
20
|
+
/** Maximum simultaneously running workers. Must be a positive integer. */
|
|
21
|
+
concurrency: number;
|
|
21
22
|
/**
|
|
22
23
|
* Whole-batch cancellation: unstarted items are rejected with the signal's
|
|
23
24
|
* reason without ever starting, while already-started items keep running —
|
|
@@ -37,7 +38,7 @@ type RunWithConcurrencyOptions<R = unknown> = {
|
|
|
37
38
|
* executions. Bounding starts (not just transfers) is what keeps memory flat:
|
|
38
39
|
* an item's preprocessing (hashing/buffering) only begins when a slot frees.
|
|
39
40
|
*/
|
|
40
|
-
declare function runWithConcurrency<T, R>(items: readonly T[], worker: (item: T, index: number) => Promise<R>, options: RunWithConcurrencyOptions<R>): Promise<BatchItemResult<R>[]>;
|
|
41
|
+
export declare function runWithConcurrency<T, R>(items: readonly T[], worker: (item: T, index: number) => Promise<R>, options: RunWithConcurrencyOptions<R>): Promise<BatchItemResult<R>[]>;
|
|
41
42
|
//#endregion
|
|
42
43
|
//#region src/progress.d.ts
|
|
43
44
|
/**
|
|
@@ -50,26 +51,32 @@ declare function runWithConcurrency<T, R>(items: readonly T[], worker: (item: T,
|
|
|
50
51
|
*/
|
|
51
52
|
type AttachmentStage = "hashing" | "reserving" | "uploading" | "requesting-download-target" | "downloading" | "done" | "error";
|
|
52
53
|
type AttachmentProgress = {
|
|
53
|
-
stage: AttachmentStage;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
stage: AttachmentStage;
|
|
55
|
+
/** Bytes moved so far within `stage`. */
|
|
56
|
+
loaded: number;
|
|
57
|
+
/** Denominator for `stage`: file.size for uploads, header.sizeBytes for downloads. */
|
|
58
|
+
total: number | undefined;
|
|
59
|
+
/** True when the stage is running but bytes cannot (yet) be observed. */
|
|
60
|
+
indeterminate: boolean;
|
|
61
|
+
/** Only on a terminal `done` where dedup skipped the transfer entirely. */
|
|
57
62
|
deduped?: true;
|
|
58
63
|
};
|
|
59
64
|
type AttachmentProgressListener = (progress: AttachmentProgress) => void;
|
|
60
65
|
type AttachmentProgressOptions = {
|
|
61
|
-
onProgress?: AttachmentProgressListener;
|
|
66
|
+
onProgress?: AttachmentProgressListener;
|
|
67
|
+
/** Min ms between byte events within a stage. Default 100. 0 emits every observation. */
|
|
62
68
|
throttleMs?: number;
|
|
63
69
|
};
|
|
64
|
-
declare const DEFAULT_PROGRESS_THROTTLE_MS = 100;
|
|
70
|
+
export declare const DEFAULT_PROGRESS_THROTTLE_MS = 100;
|
|
65
71
|
/**
|
|
66
72
|
* Fraction in 0..1 for a progress event. Indeterminate and unknown-total
|
|
67
73
|
* events read as 0 — a bar cannot honestly show a position it does not know.
|
|
68
74
|
* A zero total is complete by definition (the dedup case moves no bytes).
|
|
69
75
|
*/
|
|
70
|
-
declare function progressFraction(progress: AttachmentProgress): number;
|
|
76
|
+
export declare function progressFraction(progress: AttachmentProgress): number;
|
|
71
77
|
type ProgressEmitterOptions = AttachmentProgressOptions & {
|
|
72
|
-
/** Injected clock so throttle tests stay synchronous. */
|
|
78
|
+
/** Injected clock so throttle tests stay synchronous. */
|
|
79
|
+
now?: () => number;
|
|
73
80
|
};
|
|
74
81
|
/**
|
|
75
82
|
* Emits `AttachmentProgress` for one single-item operation, upholding:
|
|
@@ -86,7 +93,7 @@ type ProgressEmitterOptions = AttachmentProgressOptions & {
|
|
|
86
93
|
* ordering, and is unnecessary because `finish()` always emits
|
|
87
94
|
* `loaded === total`, making a dropped last intra-stage tick invisible.
|
|
88
95
|
*/
|
|
89
|
-
declare class ProgressEmitter {
|
|
96
|
+
export declare class ProgressEmitter {
|
|
90
97
|
private readonly listener;
|
|
91
98
|
private readonly throttleMs;
|
|
92
99
|
private readonly now;
|
|
@@ -128,7 +135,9 @@ declare class ProgressEmitter {
|
|
|
128
135
|
private emit;
|
|
129
136
|
}
|
|
130
137
|
type ByteProgressHooks = {
|
|
131
|
-
/** Cumulative bytes handed to the consumer. */
|
|
138
|
+
/** Cumulative bytes handed to the consumer. */
|
|
139
|
+
onBytes?: (loaded: number) => void;
|
|
140
|
+
/** The source ended and every byte reached the consumer. */
|
|
132
141
|
onDone?: () => void;
|
|
133
142
|
onError?: (error: unknown) => void;
|
|
134
143
|
};
|
|
@@ -150,10 +159,10 @@ type ByteProgressHooks = {
|
|
|
150
159
|
* Returns `source` unchanged when no hook is supplied, so a caller with no
|
|
151
160
|
* listener pays nothing.
|
|
152
161
|
*/
|
|
153
|
-
declare function withByteProgress(source: ReadableStream<Uint8Array>, hooks: ByteProgressHooks): ReadableStream<Uint8Array>;
|
|
162
|
+
export declare function withByteProgress(source: ReadableStream<Uint8Array>, hooks: ByteProgressHooks): ReadableStream<Uint8Array>;
|
|
154
163
|
//#endregion
|
|
155
164
|
//#region src/client.d.ts
|
|
156
|
-
type PreprocessResult = {
|
|
165
|
+
export type PreprocessResult = {
|
|
157
166
|
ref: AttachmentRef;
|
|
158
167
|
hash: AttachmentHash;
|
|
159
168
|
sizeBytes: number;
|
|
@@ -161,10 +170,11 @@ type PreprocessResult = {
|
|
|
161
170
|
data: ReadableStream<Uint8Array>;
|
|
162
171
|
stream: () => ReadableStream<Uint8Array>;
|
|
163
172
|
};
|
|
164
|
-
type AttachmentUploadInput = {
|
|
173
|
+
export type AttachmentUploadInput = {
|
|
165
174
|
file: Blob;
|
|
166
175
|
fileName?: string;
|
|
167
|
-
mimeType?: string;
|
|
176
|
+
mimeType?: string;
|
|
177
|
+
/** Per-item cancellation, checked between stages. */
|
|
168
178
|
signal?: AbortSignal;
|
|
169
179
|
};
|
|
170
180
|
/**
|
|
@@ -175,7 +185,7 @@ type AttachmentUploadInput = {
|
|
|
175
185
|
* upload. Passing the result back in skips the hashing stage rather than
|
|
176
186
|
* faking a zero-millisecond one.
|
|
177
187
|
*/
|
|
178
|
-
type AttachmentPreprocessedUploadInput = {
|
|
188
|
+
export type AttachmentPreprocessedUploadInput = {
|
|
179
189
|
preprocessed: PreprocessResult;
|
|
180
190
|
signal?: AbortSignal;
|
|
181
191
|
};
|
|
@@ -183,7 +193,7 @@ type AttachmentPreprocessedUploadInput = {
|
|
|
183
193
|
* Every remote download names the document that authorizes its ref; batches
|
|
184
194
|
* may freely mix documents because the anchor travels with each item.
|
|
185
195
|
*/
|
|
186
|
-
type AttachmentDownloadInput = {
|
|
196
|
+
export type AttachmentDownloadInput = {
|
|
187
197
|
documentId: string;
|
|
188
198
|
ref: AttachmentRef;
|
|
189
199
|
signal?: AbortSignal;
|
|
@@ -194,25 +204,27 @@ type AttachmentDownloadInput = {
|
|
|
194
204
|
* blob-producing conveniences let callers override what the server header
|
|
195
205
|
* reports from upload time.
|
|
196
206
|
*/
|
|
197
|
-
type AttachmentBlobOptions = {
|
|
207
|
+
export type AttachmentBlobOptions = {
|
|
198
208
|
mimeType?: string;
|
|
199
209
|
};
|
|
200
|
-
type AttachmentSaveOptions = {
|
|
210
|
+
export type AttachmentSaveOptions = {
|
|
201
211
|
fileName?: string;
|
|
202
212
|
mimeType?: string;
|
|
203
213
|
};
|
|
204
|
-
type AttachmentBlobResult = {
|
|
214
|
+
export type AttachmentBlobResult = {
|
|
205
215
|
blob: Blob;
|
|
206
216
|
header: AttachmentHeader;
|
|
207
217
|
};
|
|
208
|
-
type AttachmentObjectUrl = {
|
|
209
|
-
/** Ready for img/iframe/video src. Pins memory until revoke() is called. */
|
|
218
|
+
export type AttachmentObjectUrl = {
|
|
219
|
+
/** Ready for img/iframe/video src. Pins memory until revoke() is called. */
|
|
220
|
+
url: string;
|
|
210
221
|
header: AttachmentHeader;
|
|
211
222
|
revoke: () => void;
|
|
212
223
|
};
|
|
213
|
-
type AttachmentShareLinkInput = {
|
|
224
|
+
export type AttachmentShareLinkInput = {
|
|
214
225
|
documentId: string;
|
|
215
|
-
ref: AttachmentRef;
|
|
226
|
+
ref: AttachmentRef;
|
|
227
|
+
/** Requested link lifetime in seconds; the server clamps to its maximum. */
|
|
216
228
|
expiresIn?: number;
|
|
217
229
|
signal?: AbortSignal;
|
|
218
230
|
};
|
|
@@ -221,7 +233,7 @@ type AttachmentShareLinkInput = {
|
|
|
221
233
|
* expiresAtUtc, with no login and no document access. Minting one requires
|
|
222
234
|
* document read access; once minted it cannot be revoked before expiry.
|
|
223
235
|
*/
|
|
224
|
-
type AttachmentShareLink = {
|
|
236
|
+
export type AttachmentShareLink = {
|
|
225
237
|
url: string;
|
|
226
238
|
expiresAtUtc: string;
|
|
227
239
|
};
|
|
@@ -231,8 +243,10 @@ type AttachmentShareLink = {
|
|
|
231
243
|
* a presentation decision the client cannot make, and a confirmed dedup would
|
|
232
244
|
* shrink the denominator mid-flight.
|
|
233
245
|
*/
|
|
234
|
-
type AttachmentBatchOptions = {
|
|
235
|
-
/** Bounds preprocessing and transfer together. Defaults to 4. */
|
|
246
|
+
export type AttachmentBatchOptions = {
|
|
247
|
+
/** Bounds preprocessing and transfer together. Defaults to 4. */
|
|
248
|
+
concurrency?: number;
|
|
249
|
+
/** Whole-batch cancellation: stops unstarted items. */
|
|
236
250
|
signal?: AbortSignal;
|
|
237
251
|
onProgress?: (progress: AttachmentProgress & {
|
|
238
252
|
index: number;
|
|
@@ -241,17 +255,18 @@ type AttachmentBatchOptions = {
|
|
|
241
255
|
* Fired as each item settles, including items the signal skipped before they
|
|
242
256
|
* started, so `settled` always reaches `total`.
|
|
243
257
|
*/
|
|
244
|
-
onItemSettled?: (counts: AttachmentBatchCounts) => void;
|
|
258
|
+
onItemSettled?: (counts: AttachmentBatchCounts) => void;
|
|
259
|
+
/** Min ms between byte events, applied per item rather than batch-wide. */
|
|
245
260
|
throttleMs?: number;
|
|
246
261
|
};
|
|
247
|
-
type AttachmentBatchCounts = {
|
|
262
|
+
export type AttachmentBatchCounts = {
|
|
248
263
|
settled: number;
|
|
249
264
|
completed: number;
|
|
250
265
|
failed: number;
|
|
251
266
|
total: number;
|
|
252
267
|
};
|
|
253
|
-
declare const DEFAULT_ATTACHMENT_BATCH_CONCURRENCY = 4;
|
|
254
|
-
interface IAttachmentClient {
|
|
268
|
+
export declare const DEFAULT_ATTACHMENT_BATCH_CONCURRENCY = 4;
|
|
269
|
+
export interface IAttachmentClient {
|
|
255
270
|
preprocess(file: Blob, opts?: {
|
|
256
271
|
fileName?: string;
|
|
257
272
|
mimeType?: string;
|
|
@@ -303,7 +318,8 @@ interface IAttachmentClient {
|
|
|
303
318
|
uploadMany(inputs: readonly AttachmentUploadInput[], options?: AttachmentBatchOptions): Promise<BatchItemResult<AttachmentUploadResult>[]>;
|
|
304
319
|
downloadMany(inputs: readonly AttachmentDownloadInput[], options?: AttachmentBatchOptions): Promise<BatchItemResult<AttachmentResponse>[]>;
|
|
305
320
|
}
|
|
306
|
-
declare function createAttachmentClient(service: IAttachmentService): IAttachmentClient;
|
|
321
|
+
export declare function createAttachmentClient(service: IAttachmentService): IAttachmentClient;
|
|
307
322
|
//#endregion
|
|
308
|
-
export { AttachmentAlreadyExists,
|
|
323
|
+
export { AttachmentAlreadyExists, type AttachmentDownloadOptions, type AttachmentDownloadTarget, type AttachmentDownloadTargetOptions, type AttachmentHeader, type AttachmentMetadata, AttachmentNotFound, AttachmentPending, type AttachmentProgress, type AttachmentProgressListener, type AttachmentProgressOptions, type AttachmentResponse, type AttachmentSendOptions, AttachmentService, type AttachmentStage, type AttachmentStatOptions, type AttachmentStatus, type AttachmentTargetHeaders, AttachmentTransferError, type AttachmentTransferStage, type AttachmentTransportConfig, type AttachmentUploadRequest, type AttachmentUploadResponse, type AttachmentUploadResult, type AttachmentUploadTarget, type AttachmentUploadTransport, type BatchItemResult, type ByteProgressHooks, type HashFirstReserveAttachmentOptions, HashMismatch, type IAttachmentReader, type IAttachmentService, type IAttachmentStore, type IAttachmentTransport, type IAttachmentTransportFactory, type IAttachmentUpload, type IAttachmentUploadFactory, type IReservationStore, InvalidAttachmentRef, NullAttachmentTransport, type ParsedRef, type ProgressEmitterOptions, RemoteAttachmentStore, RemoteAttachmentUpload, RemoteAttachmentUploadFactory, RemoteReservationStore, type Reservation, ReservationNotFound, type ReserveAttachmentOptions, type RunWithConcurrencyOptions, SizeMismatch, SwitchboardAttachmentTransport, type SwitchboardClientConfig, type SwitchboardTransportConfig, type TransportFetchResult, type TransportResponse, type UploadFirstReserveAttachmentOptions, UploadTooLarge, type XhrUploadTransportOptions, createFetchUploadTransport, createRef, createRemoteAttachmentService, createXhrUploadTransport, parseAttachmentDownloadTarget, parseAttachmentUploadTarget, parseRef };
|
|
324
|
+
export {};
|
|
309
325
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","names":[],"sources":["../src/concurrency.ts","../src/progress.ts","../src/client.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","names":[],"sources":["../src/concurrency.ts","../src/progress.ts","../src/client.ts"],"mappings":";;;;;;;;;KAKY,gBAAgB;EACtB;EAAe;EAAqB,OAAO;;EAC3C;EAAe;EAAoB;;KAE7B,0BAA0B;;EAEpC;;;;;;EAMA,SAAS;;;;;;;EAOT,aAAa,QAAQ,gBAAgB;;;;;;;wBAQjB,mBAAmB,GAAG,GAC1C,gBAAgB,KAChB,SAAS,MAAM,GAAG,kBAAkB,QAAQ,IAC5C,SAAS,0BAA0B,KAClC,QAAQ,gBAAgB;;;;;;;;;;;KC3Bf;KASA;EACV,OAAO;;EAEP;;EAEA;;EAEA;;EAEA;;KAGU,8BAA8B,UAAU;KAExC;EACV,aAAa;;EAEb;;qBAGW;;;;;;wBAOG,iBAAiB,UAAU;KAS/B,yBAAyB;;EAEnC;;;;;;;;;;;;;;;;;qBAkBW;mBACM;mBACA;mBACA;UAET;UACA;UACA;UACA;UACA;UACA;EAER,YAAY,UAAU;;MAOlB;;;;;;EASJ,MAAM,OAAO,iBAAiB;IAAY;;;;;;EAkB1C,MAAM,gBAAgB;;;;;EAoBtB,OAAO;IAAa;IAAiB;IAAgB;;;EAerD;;EAKA;UAWQ;;KAME;;EAEV,WAAW;;EAEX;EACA,WAAW;;;;;;;;;;;;;;;;;;;;wBAqBG,iBACd,QAAQ,eAAe,aACvB,OAAO,oBACN,eAAe;;;YCjGN;EACV,KAAK;EACL,MAAM;EACN;EACA,SAAS;EACT,MAAM,eAAe;EACrB,cAAc,eAAe;;YAGnB;EACV,MAAM;EACN;EACA;;EAEA,SAAS;;;;;;;;;;YAWC;EACV,cAAc;EACd,SAAS;;;;;;YAOC;EACV;EACA,KAAK;EACL,SAAS;;;;;;;;YASC;EACV;;YAGU;EACV;EACA;;YAGU;EACV,MAAM;EACN,QAAQ;;YAGE;;EAEV;EACA,QAAQ;EACR;;YAGU;EACV;EACA,KAAK;;EAEL;EACA,SAAS;;;;;;;YAQC;EACV;EACA;;;;;;;;YASU;;EAEV;;EAEA,SAAS;EACT,cAAc,UAAU;IAAuB;;;;;;EAK/C,iBAAiB,QAAQ;;EAEzB;;YAGU;EACV;EACA;EACA;EACA;;qBAGW;iBAEI;EACf,WACE,MAAM,MACN;IAAS;IAAmB;MAC3B,QAAQ;EACX,QACE,SAAS,mCACT,OAAO,QAAQ,sBAAsB,QAAQ,0BAC5C,QAAQ;;;;;;;;EAQX,OACE,OAAO,wBAAwB,mCAC/B,UAAU,4BACT,QAAQ;;;;;;;;;EASX,SACE,OAAO,yBACP,UAAU,4BACT,QAAQ;;;;;;EAMX,aACE,OAAO,yBACP,UAAU,wBAAwB,4BACjC,QAAQ;;;;;;EAMX,eACE,OAAO,yBACP,UAAU,wBAAwB,4BACjC;;;;;;EAMH,kBACE,OAAO,yBACP,UAAU,wBAAwB,4BACjC,QAAQ;;;;;;;;EAQX,aAAa,OAAO,2BAA2B,QAAQ;EACvD,WACE,iBAAiB,yBACjB,UAAU,yBACT,QAAQ,gBAAgB;EAC3B,aACE,iBAAiB,2BACjB,UAAU,yBACT,QAAQ,gBAAgB;;wBA6Xb,uBACd,SAAS,qBACR"}
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as parseRef, c as AttachmentPending, f as InvalidAttachmentRef, h as UploadTooLarge, i as createRef, l as AttachmentTransferError, m as SizeMismatch, o as AttachmentAlreadyExists, p as ReservationNotFound, s as AttachmentNotFound, t as SwitchboardAttachmentTransport, u as HashMismatch } from "./switchboard-attachment-transport-BH3ZAmxE.js";
|
|
2
|
+
import { a as RemoteAttachmentUpload, c as createFetchUploadTransport, d as parseAttachmentUploadTarget, i as RemoteAttachmentUploadFactory, l as AttachmentService, n as createRemoteAttachmentService, o as RemoteReservationStore, r as RemoteAttachmentStore, s as createXhrUploadTransport, t as NullAttachmentTransport, u as parseAttachmentDownloadTarget } from "./null-attachment-transport-BGG9cRC-.js";
|
|
3
|
+
import { t as sha256Hex } from "./hash-CKHeI4AH.js";
|
|
2
4
|
//#region src/concurrency.ts
|
|
3
5
|
/**
|
|
4
6
|
* Runs `worker` over `items` with a hard upper bound on simultaneous
|
|
@@ -24,10 +26,11 @@ async function runWithConcurrency(items, worker, options) {
|
|
|
24
26
|
continue;
|
|
25
27
|
}
|
|
26
28
|
try {
|
|
29
|
+
const value = await worker(items[index], index);
|
|
27
30
|
results[index] = {
|
|
28
31
|
index,
|
|
29
32
|
status: "fulfilled",
|
|
30
|
-
value
|
|
33
|
+
value
|
|
31
34
|
};
|
|
32
35
|
} catch (error) {
|
|
33
36
|
results[index] = {
|
|
@@ -238,6 +241,21 @@ function isAttachmentAlreadyExists(err) {
|
|
|
238
241
|
if (err instanceof AttachmentAlreadyExists) return true;
|
|
239
242
|
return err instanceof Error && err.name === "AttachmentAlreadyExists" && typeof err.hash === "string" && typeof err.ref === "string";
|
|
240
243
|
}
|
|
244
|
+
function dedupedHeader(hash, options) {
|
|
245
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
246
|
+
return {
|
|
247
|
+
hash,
|
|
248
|
+
mimeType: options.mimeType,
|
|
249
|
+
fileName: options.fileName,
|
|
250
|
+
sizeBytes: options.sizeBytes,
|
|
251
|
+
extension: options.extension ?? null,
|
|
252
|
+
status: "available",
|
|
253
|
+
source: "local",
|
|
254
|
+
createdAtUtc: now,
|
|
255
|
+
lastAccessedAtUtc: now,
|
|
256
|
+
expiresAtUtc: null
|
|
257
|
+
};
|
|
258
|
+
}
|
|
241
259
|
function streamFromBuffer(buf) {
|
|
242
260
|
return new ReadableStream({ start(controller) {
|
|
243
261
|
controller.enqueue(buf);
|
|
@@ -277,14 +295,14 @@ function batchRunOptions(total, options) {
|
|
|
277
295
|
};
|
|
278
296
|
}
|
|
279
297
|
var AttachmentClientImpl = class {
|
|
298
|
+
service;
|
|
280
299
|
constructor(service) {
|
|
281
300
|
this.service = service;
|
|
282
301
|
}
|
|
283
302
|
async preprocess(file, opts) {
|
|
284
303
|
const buf = await file.arrayBuffer();
|
|
285
304
|
const bytes = new Uint8Array(buf);
|
|
286
|
-
const
|
|
287
|
-
const hash = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
305
|
+
const hash = await sha256Hex(bytes);
|
|
288
306
|
const ref = createRef(hash);
|
|
289
307
|
const sizeBytes = file.size;
|
|
290
308
|
const options = {
|
|
@@ -317,13 +335,12 @@ var AttachmentClientImpl = class {
|
|
|
317
335
|
};
|
|
318
336
|
} catch (err) {
|
|
319
337
|
if (!isAttachmentAlreadyExists(err)) throw err;
|
|
320
|
-
const header = await this.service.stat(err.ref);
|
|
321
338
|
return {
|
|
322
339
|
kind: "deduped",
|
|
323
340
|
result: {
|
|
324
341
|
hash: err.hash,
|
|
325
342
|
ref: err.ref,
|
|
326
|
-
header
|
|
343
|
+
header: dedupedHeader(err.hash, options)
|
|
327
344
|
}
|
|
328
345
|
};
|
|
329
346
|
}
|