-
Notifications
You must be signed in to change notification settings - Fork 9
Implement a prototype of CFC #1140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ba5860c
451f43f
d1fb1e4
50d89e8
530bd98
9c5f28e
67750ae
b3bedfd
0cd0929
567c0e4
666b8d5
9b7d90b
cd10dd5
e834723
c44c166
f0aa7fa
644f3b2
0b9b777
36739f3
7a54971
e1827d6
9cec592
62f0d89
bf4b159
0ad20f4
087354e
0ef70a0
4ec1ead
d5422a6
5180e20
90a7bfc
d960821
7e2c203
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -222,7 +222,11 @@ export function scrub(data: any): any { | |
| // If this resulted in an empty schema, return without a schema | ||
| return data.asSchema( | ||
| Object.keys(scrubbed).length > 0 | ||
| ? { ...data.schema, properties: scrubbed } | ||
| ? { | ||
| ...data.schema, | ||
| properties: scrubbed, | ||
| additionalProperties: false, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isn't
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It isn't the default, though I thought it was prior to this patch.
The guide page is a bit more direct:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh!, thanks for clarifying, I did indeed have it the other way around. Sigh. Makes schemas even longer by default…
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, or maybe we should flip it in our case, at least for the query case:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm inclined to align with the standard here, even though it's more verbose. |
||
| } | ||
| : undefined, | ||
| ); | ||
| } else { | ||
|
|
@@ -238,7 +242,8 @@ export function scrub(data: any): any { | |
| (key) => [key, {}], | ||
| ), | ||
| ), | ||
| } satisfies JSONSchema; | ||
| additionalProperties: false, | ||
| } as const satisfies JSONSchema; | ||
| console.log("scrubbed generated schema", scrubbed); | ||
| // Only if we found any properties, return the scrubbed schema | ||
| return Object.keys(scrubbed).length > 0 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| // Load .env file | ||
| import { parseArgs } from "@std/cli/parse-args"; | ||
| import { type DID, Identity } from "@commontools/identity"; | ||
| import { Provider as CachedStorageProvider } from "../runner/src/storage/cache.ts"; | ||
| // Some examples of how you can use this to play with the classification labels | ||
| // Store the empty list | ||
| // > deno task curl --spaceName robin --data '[]' ct://did:key:z6MkjMowGqCog2ZfvNBNrx32p2Fa2bKR1nT7pUWiPQFWzVAg/baedreih5ute2slgsylwtbszccarx6ky2ca3mtticxug6sfj3nwamacefmn/application/json | ||
| // Mark the entity secret | ||
| // > deno task curl --spaceName robin --raw --data '{"classification": ["secret"]}' ct://did:key:z6MkjMowGqCog2ZfvNBNrx32p2Fa2bKR1nT7pUWiPQFWzVAg/baedreih5ute2slgsylwtbszccarx6ky2ca3mtticxug6sfj3nwamacefmn/application/label+json | ||
| // Check the classification labels (requires the classification labels) | ||
| // > deno task curl --spaceName robin --raw --schema '{"ifc": {"classification": ["secret"]}}' ct://did:key:z6MkjMowGqCog2ZfvNBNrx32p2Fa2bKR1nT7pUWiPQFWzVAg/baedreih5ute2slgsylwtbszccarx6ky2ca3mtticxug6sfj3nwamacefmn/application/label+json | ||
| // Get the original empty list back (requires the classification labels) | ||
| // > deno task curl --spaceName robin --schema '{"ifc": {"classification": ["secret"]}}' ct://did:key:z6MkjMowGqCog2ZfvNBNrx32p2Fa2bKR1nT7pUWiPQFWzVAg/baedreih5ute2slgsylwtbszccarx6ky2ca3mtticxug6sfj3nwamacefmn/application/json | ||
| const flags = parseArgs(Deno.args, { | ||
| string: [ | ||
| "spaceName", | ||
| "key", | ||
| "data", | ||
| "schema", | ||
| ], | ||
| boolean: ["admin"], | ||
| default: { the: "application/json", admin: false, raw: false }, | ||
| }); | ||
|
|
||
| const toolshedUrl = Deno.env.get("TOOLSHED_API_URL") ?? | ||
| "http://localhost:8000/"; | ||
|
|
||
| const ANYONE = "common user"; | ||
| const remoteStorageUrl = new URL(toolshedUrl); | ||
|
|
||
| function usage() { | ||
| console.log( | ||
| "Usage: curl [--key <keyfile>] [--spaceName <spaceName>] [--admin] [--raw] [--schema <schema>] [--data <data>] url\n" + | ||
| "Example URL: ct://did:key:z6MkjMowGqCog2ZfvNBNrx32p2Fa2bKR1nT7pUWiPQFWzVAg/baedreihxpwcmhvzpf5weuf4ceow4zbahqikvu5ploox36ipeuvqnminyba/application/json\n" + | ||
| "If you provide a spaceDID in the URL, you must either be using the admin flag, or provide the --spaceName option.\n" + | ||
| "You can also provide a spaceName in the URL if it does not include any colon or slash characters. In this case, you do not need to provide the --spaceName option.", | ||
| ); | ||
| } | ||
| async function main() { | ||
| const url = flags._[0]; | ||
| if (!url || typeof url !== "string") { | ||
| console.error("Must provide an url"); | ||
| usage(); | ||
| Deno.exit(1); | ||
| } | ||
| // Parse the url like ct://spaceDID/entityID/attribute | ||
| // did key is base58btc; entity id is base32; attribute is mime type-ish | ||
| const urlRegex: RegExp = | ||
| /^(ct:\/\/)?((?<spaceDID>(did:key:[1-9A-HJ-NP-Za-km-z]+))|(?<spaceName>[^/:]+))\/(?<of>[a-z2-7]+)(\/(?<the>\w+\/[-+.\w]+))?$/; | ||
| const match = url.match(urlRegex); | ||
| if (match === null || match.groups === undefined) { | ||
| console.error("Invalid url"); | ||
| Deno.exit(1); | ||
| } | ||
| const entityId = { "/": match.groups.of }; | ||
| const the = (match.groups.the && match.groups.the !== "") | ||
| ? match.groups.the | ||
| : "application/json"; | ||
| if (!match.groups.spaceName && !match.groups.spaceDID) { | ||
| console.error("No space name or space DID found"); | ||
| Deno.exit(1); | ||
| } | ||
|
|
||
| let identity: Identity; | ||
| if (flags.key) { | ||
| try { | ||
| const pkcs8Key = await Deno.readFile(flags.key); | ||
| identity = await Identity.fromPkcs8(pkcs8Key); | ||
| } catch (e) { | ||
| console.error( | ||
| `Could not read key at ${flags.key}.`, | ||
| ); | ||
| Deno.exit(1); | ||
| } | ||
| } else { | ||
| identity = await Identity.fromPassphrase(ANYONE); | ||
| } | ||
|
|
||
| // Actual identity is derived from space name if we don't provide an admin key | ||
| if (!flags.admin) { | ||
| const spaceName = match.groups.spaceName ?? flags.spaceName; | ||
| identity = await identity.derive(spaceName); | ||
| } | ||
| const spaceDID = match.groups.spaceDID | ||
| ? match.groups.spaceDID as DID | ||
| : identity.did(); | ||
| const schema = flags.schema ? JSON.parse(flags.schema) : {}; | ||
|
|
||
| // TODO(@ubik2) - this constrains us to values that are json | ||
| // need to revisit for image or blob support | ||
| const putData = flags.data ? JSON.parse(flags.data) : undefined; | ||
|
|
||
| const storageId = crypto.randomUUID(); | ||
| const provider = new CachedStorageProvider({ | ||
| id: storageId, | ||
| address: new URL("/api/storage/memory", remoteStorageUrl), | ||
| space: spaceDID, | ||
| as: identity, | ||
| the: the, | ||
| settings: { | ||
| maxSubscriptionsPerSpace: 50_000, | ||
| connectionTimeout: 30_000, | ||
| useSchemaQueries: true, | ||
| }, | ||
| }); | ||
| if (!putData) { | ||
| const result = await provider.sync(entityId, true, { | ||
| schema: schema, | ||
| rootSchema: schema, | ||
| }); | ||
| if (result.error) { | ||
| console.log("Failed to sync object", result.error); | ||
| } | ||
| const storageValue = provider.get(entityId); | ||
| const data = flags.raw ? storageValue : storageValue?.value; | ||
|
|
||
| console.log(JSON.stringify(data)); | ||
| Deno.exit(0); | ||
| } else { | ||
| // The entries in the database all get a value key and store their value there, | ||
| // so we need to do the same for the value we provide to StorageValue for send. | ||
| const result = await provider.send([{ | ||
| entityId: entityId, | ||
| value: flags.raw ? putData : { value: putData }, | ||
| }]); | ||
| if (result.ok) { | ||
| const putDataJSON = JSON.stringify(putData); | ||
| console.log( | ||
| `Stored ${putDataJSON} at ct://${spaceDID}/${entityId["/"]}/${the}`, | ||
| ); | ||
| } else { | ||
| console.error("Failed to put data:", result.error); | ||
| } | ||
| Deno.exit(0); | ||
| } | ||
| } | ||
| main(); |
Uh oh!
There was an error while loading. Please reload this page.